Analysis of Shiro security framework

Keywords: Shiro Session Java Database

Shiro function introduction

The following describes the meaning of each function point:

Authentication: authentication / login, to verify whether the user has the corresponding identity;

Authorization: authorization, that is, permission verification, to verify whether an authenticated user has a certain permission; that is to say, to determine whether the user can do something, for example, fine-grained verification of whether a user has a certain permission for a resource.

*Session Management: * callback management, that is, a session is a session after the user logs in. Before he exits, all his information is in the session; the session can be a Java se environment or a web environment.

Cryptography: encryption, to protect the security of data; for example, password encryption is stored in the database, not in plaintext.

Web Support: Web Support, which can be easily integrated into the web environment.

Caching: caching. For example, after a user logs in, the user information and the role / permission they have do not need to be checked every time. They can be retrieved in the cache to provide efficiency.

Concurrency: shiro supports multi-threaded concurrent verification, that is, if you open another thread in one thread, you can automatically propagate the permissions.

Testing: provide test support;

Run As: allows one user to pretend to be another user if they allow access.

Remember Me, this is a very common function, that is, after one login, you don't need to log in the next time.

Shiro architecture

① Shiro is a Java security framework

Functions that can be completed: Authentication, Authorization, encryption, session management, web support, caching, remember me.

② How to work with Shiro


The core of the subject shiro external API is subject. Subject represents current use. This login object may not be a person, but a web crawler, robot, etc. All interactions with subject will be delegated to SecurityManager. Subject is the facade, and SecurityManager is the real executor.

SecurityManager Security Manager:
All security related operations will be placed in the SecurityManager for interaction;
Manage all subjects;
It is the core of Shiro and is responsible for interacting with other components of Shiro, which is equivalent to the dispatcher servlet in spring MVC.

Realm Shiro obtains security data (roles, users, permissions) from the realm. To verify the user's identity, it needs to obtain the corresponding users from the realm for comparison to determine whether the user's identity is legal. It also needs to obtain the corresponding roles / permissions of the user from the realm to verify whether the user can operate. The realm can be regarded as a DataSource.
In Realm, the data is obtained from the cache first. If it can't be obtained in the cache, then it is obtained in the database, and then the data is put into the cache.

1, Subject object:

Subject subject = SecurityUtils.getSubject();
After getting the Subject object, the Subject can do many things:

  1. subject.getSession(); get the session object. After getting the session, you can store the session value in the session.
  2. subject.isAuthenticated(); determines whether the current object is authenticated, that is, whether to log in.
  3. subject.login(token); login operation
  4. subject.hasRole('role '); judge whether there is role information for the current user
  5. subject.isPermitted('user:delete:zhangsan '); judge whether the current principal has permission. Judge whether the user has some behavior. The current representative user can delete zhangsan
  6. subject.logout(); performs the logout operation

When the subject calls the login() method, there will be some exceptions. At this time, we need to manually catch these exceptions. These exceptions may include that the user name does not have an unknown accountexception, the password is incorrect, the incorrectredentialsexception account is locked, the LockedAccountException and other authentication exceptions

2, token object ():

Shiro provides us with a username password token object, which encapsulates the username password rememberMe host attribute. We can inherit and customize the token object, such as adding whether it is mobile login, adding email, alias, etc. subject determines whether the current login principal is authenticated. If no login is found, the information of the current subject object needs to be put into to In Ken, subject performs login verification on token

Shiro authentication

Identity authentication process
  • 1. First, call Subject.login(token) to log in, which will automatically delegate to SecurityManager

  • 2. The SecurityManager is responsible for the real authentication logic; it delegates to the Authenticator for authentication;

  • 3. Authenticator is the real authenticator. The core authentication entry point of Shiro API, where you can insert your own implementation;

  • 4. The Authenticator may delegate to the corresponding AuthenticationStrategy for multi Realm authentication. By default, modularealmauthenticator will call the AuthenticationStrategy for multi Realm authentication;

  • 5. The Authenticator will pass the corresponding token into the Realm and obtain the authentication information from the Realm. If there is no return / throw exception, it means that the authentication fails. Multiple realms can be configured here and will be accessed according to the corresponding order and policies.

Shiro permission comment

  • @Requireauthentication: indicates that the current Subject has been authenticated through login; that is, the Subject. isAuthenticated() returns true • @ RequiresUser: indicates that the current Subject has been authenticated or logged in by remembering me.
  • @RequiresGuest: it means that the current Subject has no authentication or has logged in by remembering that I am a tourist.
  • @RequiresRoles(value = {"admin", "user"}, logical= Logical.AND): indicates that the roles admin and user are required for the current Subject
  • @RequiresPermissions (value = {"user:a", "user:b"}, logical= Logical.OR): indicates that the current Subject needs permission user:a or user:b.

Shiro interceptor rules

ShiroConfig

Note: the config class of this article must be handed over to the spring container for management

①ShiroFilterFactoryBean
  • Shiro provides support for integration with the Web. It intercepts URL s requiring security control through a ShiroFilter entry, and then controls them accordingly

  • Example:

@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
    ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
    // Shiro's core security interface. This property is required (the security manager was directly handed over to the spring container for management, so it was directly obtained from it)
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    //If the authentication fails, go to the configuration of the login page
    shiroFilterFactoryBean.setLoginUrl("/login");
    //If the login is successful, the default page will jump to "/" if not configured
    shiroFilterFactoryBean.setSuccessUrl("");
    //Pages that do not have permission to jump by default
    shiroFilterFactoryBean.setUnauthorizedUrl("/error/403");
    //Custom configuration interceptor chain, pay attention to the order (different order has influence) - for enumeration name field, please check the intercepting rules
    //When Shiro validates the URL, if the URL matches successfully, it will no longer continue to match the lookup (so pay attention to the URL order in the configuration file, especially when using wildcards)
    Map<String, String> map = new LinkedHashMap<>(16,1);
    map.put("/logout", "logout");
    map.put("/submit","anon");
    map.put("/login", "anon");
    map.put("/gifCode", "anon");
    map.put("/css/**", "anon");
    map.put("/fonts/**", "anon");
    map.put("/img/**", "anon");
    map.put("/js/**", "anon");
    map.put("/lib/**", "anon");
    //All requests require user authentication
    map.put("/**", "user");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
    return shiroFilterFactoryBean;
}
②DefaultWebSecurityManager
  • The DefaultWebSecurityManager class mainly defines such operations as setting subjectDao, getting session mode, setting session mode, setting session manager, and whether it is http session mode. It inherits the DefaultSecurityManager class and implements the WebSecurityManager interface

  • Example:

    @Bean
    public DefaultWebSecurityManager getSecurityManager(){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        //See the following code for setrealm (REALM) - > getuserrealm (hashedcredentialsmatcher matcher) method
        securityManager.setRealm(getUserRealm(hashedCredentialsMatcher()));
        return securityManager;
    }
    
③ Realm (it will not be described in detail here, but it will be described later)
  • Shiro obtains security data (such as user, role and permission) from the Realm, that is, to verify the user's identity, the SecurityManager needs to obtain the corresponding user from the Realm for comparison to determine whether the user's identity is legal; it also needs to obtain the corresponding role / permission of the user from the Realm to verify whether the user can operate; the Realm can be regarded as a DataSource

  • Example:

    @Bean
    //UserRealm is the authorizing Realm inherited by its own definition (see Realm for details of overriding methods after inheritance)
    public UserRealm getUserRealm(HashedCredentialsMatcher matcher){
        UserRealm userRealm=new UserRealm();
        userRealm.setCredentialsMatcher(matcher);
        return userRealm;
    }
    
④HashedCredentialsMatcher
  • Shiro provides the CredentialsMatcher interface for encryption and password authentication services, and HashedCredentialsMatcher is an implementation class of CredentialsMatcher. When writing a project, we always use asymmetric encryption of user password. At present, the mainstream asymmetric encryption method is MD5 and salt addition on MD5. HashedCredentialsMatcher also allows us to specify our own algorithm and salt

  • Example:

    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //Set encryption method here (SysConstant.ALGORITHNAME=MD5)
        credentialsMatcher.setHashAlgorithmName(SysConstant.ALGORITHNAME);
        //Set encryption times here (SysConstant.HASHNUM=10)
        credentialsMatcher.setHashIterations(SysConstant.HASHNUM);
        return credentialsMatcher;
    }
    
⑤AuthorizationAttributeSourceAdvisor
  • Enable Shiro's annotation support, such as @ RequireRoles @RequireUsers

  • Example:

    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    

Realm detailed explanation

  • Shiro obtains security data (such as user, role and permission) from the Realm, that is, to verify the user's identity, the SecurityManager needs to obtain the corresponding user from the Realm for comparison to determine whether the user's identity is legal; it also needs to obtain the corresponding role / permission of the user from the Realm to verify whether the user can operate; the Realm can be regarded as a DataSource

  • Realm inheritance relationship

Generally speaking, it inherits the authorizing realm (authorization); it inherits the authenticating realm (authentication), and indirectly inherits the caching realm (with cache implementation), rewriting the doGetAuthenticationInfo authentication and doGetAuthenticationInfo authorization methods.

  • doGetAuthorizationInfo authorization method

  • Authorization process:

  • Example:

    @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            /**
             * 1. Get current login user information
             * 2. Get the roles and permissions associated with the current user
             * 3. Encapsulate roles and permissions into the AuthorizationInfo object and return
             */
            User user = (User) SecurityUtils.getSubject().getPrincipal();
            SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
            //The following comment codes are mainly roles: permission control
            /**
             *    //Get user role permissions
             *         /**
             *          for(PositionVO position : positions){
             *             //position
             *             roleStrings.add(position.getName());
             *             for(PermissionVO permission : position.getPermissions()){
             *                 //Permission corresponding to position
             *                 permissionStrings.add(position.getName() + ":" +  		   permission.getName());
             *             }
             *         }
             * 			authorizationInfo.setRoles(roleStrings);
             * 			authorizationInfo.setStringPermissions(permissionStrings);
             */
            //Here is the main control of the role
            List<Role> userRole = roleService.findUserRole(user.getId());
            simpleAuthorizationInfo.addStringPermission(userRole.get(0).getRoleName());
            return simpleAuthorizationInfo;
        }
    
  • doGetAuthenticationInfo authentication method (doGetAuthenticationInfo implements some businesses, such as determining whether users are locked, etc.)

  • Certification process:

  • Example:

     @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
         //AuthenticationToken represents the ticket used when logging in. Only one implementation in shiro is UsernamePasswordToken
           if (!(authenticationToken instanceof UsernamePasswordToken)) {
                throw new UnknownAccountException();
            }
            /**
             * 1. Get the entered user name from token
             * 2. Get user object by querying database with username
             * 3. Call Authenticator for password verification
             */
            //Get user name
            String username= (String) authenticationToken.getPrincipal();
            User byNameUser = userService.findByName(username);
            // Session session = SecurityUtils.getSubject().getSession(); 
            //session.setAttribute(SESSION_LOGIN_CUSTOMER_ID, user.getId());
            //You can use session to store user information in shiro's session for easy access (stored in the server). JWT user information stored in token (client)
            if (byNameUser==null){
                throw new UnknownAccountException(String.valueOf(UavStatesEnums.ACCOUNT_UNKNOWN.getMessage()));
            }
            /**
             * Submit to Shiro for password decryption verification
             * Calling SecurityUtils.getSubject().getPrincipal() encountered a type conversion problem and reported an error string! = > User
             * Please refer to this article: {@ link https://blog.csdn.net/qq_/article/details/78634575}
             */
            return new SimpleAuthenticationInfo(byNameUser,byNameUser.getPassword(), ByteSource.Util.bytes(byNameUser.getSalt()),getName());
        }
    

    Log in to Controller

    • There are many operations that can be done in the login layer. Here is just a simple login function

    • Example:

    public ResponseCode tologin(@RequestBody User user){
        try {
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token=new 		 UsernamePasswordToken(user.getUsername(),user.getPassword());
            subject.logout();
            subject.login(token);
            return ResponseCode.success(super.getToken());
        } catch (UnknownAccountException e){
            return ResponseCode.accountunkonw();
        }catch (IncorrectCredentialsException e){
            return ResponseCode.usererror();
        }
    }
    

    summary

    Shiro is a full-featured framework, which is easy to use, but it is quite difficult to use it well. I hope that if it is not complicated to use, it will not be too complex to write Shiro code structure. The above code is a complete set of codes for Shiro to simply log in, which can be used directly. This talent is not familiar with it. If there is any error, please point out.

Published 14 original articles, won praise 4, visited 405
Private letter follow

Posted by rednaxel on Tue, 14 Jan 2020 22:12:51 -0800