Personal experience sharing of using shiro to customize filter under springboot
- 2021-11-14 06:00:50
- OfStack
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).