Personal experience sharing of using shiro to customize filter under springboot

  • 2021-11-14 06:00:50
  • OfStack

Directory Objectives Step 1. Add shiro dependencies to pom. xml 2. Create ShiroRealm. java 3. Create ShiroConfiguration. java 4. Create custom filters MyFilter. java 5. Step 3 uses custom password validation6. Step 3 releases personal experience of filtering login page/loginController

shiro is used in springboot, and because there is no xml configuration file, the method used is somewhat different from that in spring. After stepping on countless pits, the steps of using shiro under springboot are summarized as follows.

Because my understanding of shiro is not very deep, I did not continue to study after realizing the work requirements, so there may be omissions or mistakes, please forgive me.

Objectives

Using shiro in springboot 1. Implement login authentication for users 2. Use custom filter authentication for 1 specified url (no longer use realm authentication for shiro)

Steps

1. Add shiro dependencies to pom. xml


<dependency>
   <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-spring</artifactId>
   <version>1.3.2</version>
</dependency>

2. Create ShiroRealm. java

Inherit AuthorizingRealm class and override login authentication method and authorization method


import User;
import UserService;
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.springframework.beans.factory.annotation.Autowired; 
import java.util.ArrayList;
import java.util.List;
 
public class ShiroRealm extends AuthorizingRealm {
    // Written by oneself service Use for injecting and executing database query methods 
    @Autowired
    private UserService userService;
    // Certification . Login 
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken utoken=(UsernamePasswordToken) token;// Object entered by the user token
        String username = utoken.getUsername();
        // This User Object is a custom JavaBean , using userService Get from the database User Object ( Include username, password, permissions 3 Fields )
        User user = userService.findUserByUserName(username);
        return new SimpleAuthenticationInfo(user, user.getPassword(),this.getClass().getName());// Put in shiro. Call CredentialsMatcher Verify password 
    }
    // Authorization 
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        User user=(User) principal.fromRealm(this.getClass().getName()).iterator().next();// Get session Users in 
        List<String> permissions=new ArrayList<>();
        // Use a custom user Object gets the permission field, string Type, load the collection 
        permissions.add(user.getRole());
 
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        info.addStringPermissions(permissions);// Put permissions into shiro Medium .( Permission correlation is not actually used in my code )
        return info;
    }
}

3. Create ShiroConfiguration. java

Using the @ Configuration annotation, is a configuration class for shiro, similar to xml


import java.util.LinkedHashMap;
import java.util.Map; 
import org.apache.shiro.mgt.SecurityManager;
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; 
import javax.servlet.Filter;
 
@Configuration
public class ShiroConfiguration { 
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();
        //  You must set  SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
 
        /* Important, set custom interceptors when accessing some custom interceptors url Use this filter Validate */
        Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
        // If map Inside key Value is authc, Indicates all names named authc Use this custom filter
        //map Inside key Value is myFilter, Indicates all names named myFilter Use this custom filter See below for details 
        filters.put("myFilter", new MyFilter());
        shiroFilterFactoryBean.setFilters(filters);
        /*---------------------------------------------------*/
 
        // Interceptor 
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
        // Configure exit filters , The specific exit code Shiro Has been realized for us 
        filterChainDefinitionMap.put("/logout", "logout");
        //    anon: All url Can be accessed anonymously ;
        //    authc:  Authentication is required for access ;
        //    user: Configuration Remember Me or Authentication Pass can be accessed; 
        // Release the filtering of static resources 
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/img/**", "anon");
        // Release login url Filtering of 
        filterChainDefinitionMap.put("/loginController", "anon");
        ///
        // For the specified url Using custom filter Validate 
        filterChainDefinitionMap.put("/targetUrl", "myFilter");
        // You can configure multiple filter Separated by commas and filtered in sequence, the lower part indicates that it is customized first filter Verify, and then pass shiro Validation of default filters 
        //filterChainDefinitionMap.put("/targetUrl", "myFilter,authc");
        ///
        // Filter chain definition, executed sequentially from top to bottom, 1 General will  /** Put it at the bottom 
        //url Match from top to bottom, and when the condition matches successfully, it will enter the specified filter And return( Subsequent conditions will not be judged ) Therefore, this sentence needs to be at the bottom 
        filterChainDefinitionMap.put("/**", "authc");
 
        // If you do not set the default, it will automatically find Web Under the project root directory "/login.jsp" Page 
        shiroFilterFactoryBean.setLoginUrl("/login");
        //  Links to jump after successful login 
        shiroFilterFactoryBean.setSuccessUrl("/loginSuccess");
        //  Unauthorized interface 
        shiroFilterFactoryBean.setUnauthorizedUrl("/403"); 
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 
        return shiroFilterFactoryBean;
    }
 
    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        return securityManager;
    }
    // Configuring the Core Security Transaction Manager 
    @Bean(name="securityManager")
    public SecurityManager securityManager(@Qualifier("shiroRealm") ShiroRealm shiroRealm) {
        DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
        manager.setRealm(shiroRealm);
        return manager;
    }
    // Configure a custom permission logger 
    @Bean(name="shiroRealm")
    public ShiroRealm shiroRealm(@Qualifier("credentialsMatcher") CredentialsMatcher matcher) {
        ShiroRealm shiroRealm=new ShiroRealm();
        shiroRealm.setCredentialsMatcher(matcher);
        return shiroRealm;
    }
    // Configuring a Custom Password Comparator 
    @Bean(name="credentialsMatcher")
    public CredentialsMatcher credentialsMatcher() {
        return new CredentialsMatcher();
    }
}

4. Create a custom filter MyFilter. java

Inherit the AccessControlFilter class and use it in step 3


import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Set; 
public class MyFilter extends AccessControlFilter {
    private static Logger log = LoggerFactory.getLogger(MyFilter.class);
 
    // Judge whether to intercept, false To intercept, true To allow 
    @Override
    public boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object object) throws Exception {
        Subject subject = getSubject(req,resp);
        String url = getPathWithinApplication(req);
        log.info(" Is being accessed by the current user url For  " + url);
        log.info("subject.isPermitted(url);"+subject.isPermitted(url));
        // You can judge whether to intercept or not according to your needs, and you can obtain subject To determine user rights, you can also use the req Obtain request header request body information 
        //return true;
        return false;        
    }
 
    // The above method returns false Posterior ( Be intercepted ) , will enter this method; This method returns false Indicates that the processing is finished ( No release ) ; Return true Indicates that processing needs to continue ( Release )
    @Override
    public boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {     
        // From req You can also use other methods to determine whether to pass or not 
        String username = request.getParameter("name");
        String password = request.getParameter("password");
        // Create token Object 
        UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(username,password);
        Subject subject = SecurityUtils.getSubject();
        try {
            // Use subject Object's login Method verifies that the token Can I log in ( Usage 2 In shiroRealm.java Method validation in )
            subject.login(usernamePasswordToken);
        } catch (Exception e) {
            //log.info(" Log in failed ");
            //log.info(e.getMessage());
            return false;
        }
        //log.info(" Successful login ");
        return true;
    }
}

5. Custom password authentication is used in Step 3

Therefore, you need to create the class CredentialsMatcher. java (corresponding to the name in Step 3) and inherit the SimpleCredentialsMatcher class


import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher; 
public class CredentialsMatcher extends SimpleCredentialsMatcher {
 
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        UsernamePasswordToken utoken=(UsernamePasswordToken) token;
        // Get the password entered by the user :( Salt can be added (salt) The way to test )
        String inPassword = new String(utoken.getPassword());
        // Get the password in the database 
        String dbPassword=(String) info.getCredentials();
        // Compare passwords 
        return this.equals(inPassword, dbPassword);
    }
}

6. Filtering the login page/loginController was released in Step 3

So I added an ShiroController. java class


import User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; 
import javax.servlet.http.HttpSession;
 
@Controller
public class ShiroController {
    @RequestMapping("/")
    public String loginPage() {
        return "login";
    }
    @RequestMapping("/login")
    public String login() {
        return "login";
    }
    @RequestMapping("/loginController")
    public String loginUser(String username,String password,HttpSession session) {
        UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(username,password);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(usernamePasswordToken);   // Complete login 
            // Custom JavaBean Used to save user name, password, permissions 3 Fields 
            User user=(User) subject.getPrincipal();
            // Optional, can be placed in session For subsequent use 
            session.setAttribute("user", user);
            // Jump to Login Success page (controller)
            return "forward:/loginSuccess";
        } catch(Exception e) {
            // Login failed, jump back to login page (html)
            return "login";
        } 
    }
    @RequestMapping("/logOut")
    public String logOut(HttpSession session) {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        //session.removeAttribute("user");
        return "login";
    }
}

Personal experience

1. The url interception in the shiro configuration class is executed from top to bottom. If url matches one rule, the matching method will jump out and the subsequent matching rules (equivalent to return) will be ignored.

2. When using a custom filter for shiro, it is best to inherit filter from shiro instead of directly inheriting the Filter class.

3. When shiro uses custom filter, when key of map collection is configured as "authc" and value is configured as "new MyFilter ()", it means that url configured as authc is intercepted by using custom filter instead of using the authentication method in ShiroRealm (it may be that the interceptor of authc default of shiro is overwritten); Therefore, it is best to configure key as other custom strings, and set the interception rules of some url to use custom filter interception (if you still want to use the default interceptor of shiro, you can connect "authc" with commas).


Related articles: