[back end] - frame -- shiro

shiro

Rights Management Overview

Permission management realizes the control of users' access to the system and controls that users can access and only access their authorized resources according to security rules or security policies. In other words, rights management includes user identity authentication (login) and authorization (access rights to resources)
Detailed architecture in shiro:

Authentication in shiro

Key objects in the certification process

1. Subject. The user accessing the system, the subject can be a user, a program, etc., and those who authenticate are called subjects
2. Principal, identity information, is the identification of an entity for identity authentication. The identification must be unique, such as user name, mobile phone number, email, etc. An entity can have multiple identities, but it must have a primary principal
3. credential information refers to the security information that only the subject knows, such as password, certificate, etc

Code use

1. Dependence

<dependency>
     <groupId>org.apache.shiro</groupId>
     <artifactId>shiro-core</artifactId>
     <version>1.5.3</version>	
</dependency>

2. Create shiro.ini file in the resources directory
This ini file is not available after we integrate boot. It is only used to write the relevant permission data in our system when we learn shiro, that is, the data used to judge the permission in the process of identity authentication and authorization, so that we can avoid obtaining permission data from the database and reduce the pressure when we learn shiro

[users]
xiaocheng=123
zhangsan=123456
lisi=789
public class TestAuthenticator {
    public static void main(String[] args) {
        //1. Create a security manager object
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //2. Set realm for security manager
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
        //3. The securityutils global security tool class sets a security manager for it
        SecurityUtils.setSecurityManager(securityManager);
        //4. Acquisition subject
        Subject subject = SecurityUtils.getSubject();
        //5. Create a token composed of token identity information and credential information
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("xiaocheng","123");
        try {
            subject.login(usernamePasswordToken);
            System.out.println("Certification status:" + subject.isAuthenticated());
        } catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("user name does not exist");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("Voucher information(password)error");
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}

Customize realm to obtain permission data from the database

public class CustomerRealm extends AuthorizingRealm {
    //to grant authorization
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    //authentication
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //Get user name in token
        String principal = (String) authenticationToken.getPrincipal();
        System.out.println(principal);
        //Query the database using jdbc mybatis according to the user information
        String realName = "xiaocheng";
        if(realName.equals(principal)){
            //Parameter 1: return the correct user name in the database parameter 2: correct credential information (password) parameter 3: provide the name of the current realm this.getName()
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, "1235", this.getName());
            System.out.println(this.getName());
            return simpleAuthenticationInfo;
        }
        return null;
    }
}
public class TestCustomerRealmAuthenticator {
    public static void main(String[] args) {
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        defaultSecurityManager.setRealm(new CustomerRealm());
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        Subject subject = SecurityUtils.getSubject();
        //At this time, the password verification process is completed by shiro through the CredentialsMatcher credential matcher. The default matching method is the equals() method
        UsernamePasswordToken token = new UsernamePasswordToken("xiaocheng", "123");
        try {
            subject.login(token);
        }
        catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("user name does not exist");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("Voucher information(password)error");
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}

Encryption processing

1. Introduction to MD5 algorithm
Function: generally used for encryption or signature (checksum)
Features: irreversible; As long as the content is the same, the generated results are consistent no matter how many times they are executed
Generation result: it is always a hexadecimal, 32-bit string
2. Implementation idea of MD5 + salt
At the time of user registration, random salt (string) is generated at the business layer. The password entered by the user is combined with the random salt, encrypted by MD5 algorithm, and then stored in the database. The content of random salt should also be stored in the database; When the user logs in, query the ciphertext corresponding to the user name and the random salt, then splice the password entered by the user with the random salt in the same splicing method, then encrypt it with MD5, and then compare it with the queried ciphertext to complete the function of password verification
Code implementation:
1. Implement MD5 encryption verification

public class CustomerRealmMD5 extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String principal = (String) authenticationToken.getPrincipal();
        //Query operation
        if("xiaocheng".equals(principal)){
        	//At this time, the password in the database is returned, that is, the ciphertext obtained by md5 encryption of "123"
            return new SimpleAuthenticationInfo(principal,"202cb962ac59075b964b07152d234b70",this.getName());
        }
        return null;
    }
}
public class TestMD5CustomerRealm {
    public static void main(String[] args) {
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //Create a custom realm
        CustomerRealmMD5 customerRealmMD5 = new CustomerRealmMD5();
        //Create a credential matcher to verify the password, and use the hash matcher
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        //Set the encryption algorithm to md5
        matcher.setHashAlgorithmName("md5");
        //Set the credential matcher in the realm as the hash matcher we created using the md5 algorithm,
        //In this way, the password entered by the user will be md5 encrypted and then compared with the password obtained from the database, rather than simply equals()
        customerRealmMD5.setCredentialsMatcher(matcher);
        
        defaultSecurityManager.setRealm(customerRealmMD5);
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("xiaocheng","123");
        try {
            subject.login(token);
            System.out.println("Login successful");
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("user name does not exist");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("Voucher information(password)error");
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

2. Add random salt on the basis of md5

//When the identity information and encryption voucher information are returned in the realm, add parameter 3: random salt used during registration, and the random salt can be added to the entered plaintext password
return new SimpleAuthenticationInfo(principal,
        "6b630ce15fb87a3e77daaa76db4f2b43",
        ByteSource.Util.bytes("uy&^%7"),
        this.getName());

3. Add hash processing

//Set the number of hash hashes in the credential matcher to the number used when registering encryption
matcher.setHashIterations(1024);

Authorization in shiro

Authorization overview

Authorization is access control, which controls who can access which resources. After identity authentication, the subject needs to allocate permissions to access system resources. For some resources, it is impossible to access without permissions
Key objects include subjects, resources (including resource types and resource instances), and permissions / permissions (which specify the operation permissions of subjects on resources)

Authorization method

  • Role based access control (whether the principal has certain roles. Specific roles have access to specific resources)
  • Resource based access control (whether the principal has access rights to some resources)
    The implementation of this method requires a permission string:
    Writing rules: Resource Identifier: operation: resource instance identifier, which means what operations are performed on which instance of which resource. You can use * as a wildcard

code implementation

1.Role-Based

//Override the methods that the authorization process will pass through in the custom realm
@Override
//The parameter of the method is a collection of identities. As mentioned earlier, a subject can have multiple identities, but only one primary identity
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    //Primary identity, i.e. user name (xiaocheng)
    String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
    //There is only one identity at this time
    //System.out.println(principalCollection.asList().size());
    //System.out.println(primaryPrincipal);
    
    //                   Role based
    //Obtain role information and permission information according to identity information, i.e. user name, and set role information
    SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
    //Assign the role information queried in the database to the permission object
    simpleAuthorizationInfo.addRole("admin");
    simpleAuthorizationInfo.addRole("user");
    
	//                Based on permission string
	//Assign the permission information queried in the database to the permission object
	simpleAuthorizationInfo.addStringPermission("user:*:01");
	simpleAuthorizationInfo.addStringPermission("product:create");
    return simpleAuthorizationInfo;
}
try {
    subject.login(token);
    System.out.println("Login successful");
    //Authorize authenticated users
    if(subject.isAuthenticated()){
    	//                 Role based
        //Based on single role permission control, the roles in the parameters are defined in advance
        System.out.println(subject.hasRole("admin"));//true
        //Based on multi role permission control, you must have each role in the parameter at the same time
        System.out.println(subject.hasAllRoles(Arrays.asList("user", "admin")));//true
        //Based on multi role permission control, only one role in the parameters is required
        boolean[] booleans = subject.hasRoles(Arrays.asList("user", "admin", "baba"));//true true false
        for (int i = 0; i < booleans.length; i++) {
            System.out.println(booleans[i]);
        }
		
		//        Access control based on permission string
        System.out.println(subject.isPermitted("user:update:01"));//true
        System.out.println(subject.isPermitted("product:create:01"));//true
        //What permissions do you have
        boolean[] booleans1 = subject.isPermitted("user:create:01", "product:create:01", "lala:*:*");//true true false
        for(boolean b : booleans1){
            System.out.println(b);
        }
        //What permissions do you have at the same time
        System.out.println(subject.isPermittedAll("user:create:01", "product:create:01"));//true
        System.out.println(subject.isPermittedAll("user:create:01", "product:create:01","l:*:*"));//false
    }
}catch..............................

springboot integration

The main contents of integration are: configuring shiro, connecting to the database for data interaction, and realizing authentication and authorization on the basis of. Realize user registration and login related functions

Environment construction

  • rely on
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.5.3</version>
</dependency>

And the dependencies of mybatis, mysql, druid, etc

  • Configuration of application.properties
# apply name
spring.application.name=boot_shiro
# Application service WEB access port
server.port=8080

spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/boot_shiro?characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root

mybatis.type-aliases-package=com.linyuang.www.po
mybatis.mapper-locations=classpath:com/linyuang/www/mapper/*.xml

Specific code writing

  • shiro configuration class
/**
 * @author Lenovo
 * shiro Framework related configuration classes
 */
@Configuration
public class ShiroConfig {
    //Create ShiroFilter filter
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        Map<String,String> map = new HashMap<>();
        //anon set as shared resource
        map.put("/user/login","anon");
        //authc indicates that requesting this resource requires authentication and authorization
        map.put("/index.jsp","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        //The default authentication interface. If you do not have authentication and access limited resources, you will be redirected to the default authentication interface. If it is not set, the default is / login.jsp (indicating that shiro and JSP integration is still friendly)
        shiroFilterFactoryBean.setLoginUrl("/login.jsp");
        return shiroFilterFactoryBean;
    }
    //Create security manager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") Realm realm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(realm);
        return defaultWebSecurityManager;
    }
    //Create realm
    @Bean
    public Realm getRealm(){
        CustomerRealm customerRealm = new CustomerRealm();
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher("MD5");
        credentialsMatcher.setHashIterations(1024);
        customerRealm.setCredentialsMatcher(credentialsMatcher);
        return customerRealm;
    }
}

The access permission of the specified url actually uses shiro's filter:

  • Create custom realm
/**
 * @author Lenovo
 * Since the realm class is not injected into the spring container, if the bean s in the container, such as those in the business layer or persistence layer, are used in the class,
 *  The @ Autowired annotation cannot be used to implement injection. We can only get it from the core container through code
 */
public class CustomerRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //Get identity information
        String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
        //Query database for permission information
        if("xiaocheng".equals(primaryPrincipal)){
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            simpleAuthorizationInfo.addRole("admin");
            return simpleAuthorizationInfo;
        }
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String principal = (String) authenticationToken.getPrincipal();
        String credentials = (String) authenticationToken.getCredentials();
        //Get the service object in the factory and query the database
        UserService userService = (UserService) ApplicationContextUtil.getBean("userService");
        User userByUsername = userService.findUserByUsername(principal);
        if(!ObjectUtils.isEmpty(userByUsername)){
            new SimpleAuthenticationInfo(userByUsername.getUsername(),
                                        userByUsername.getPassword(),
                                        ByteSource.Util.bytes(userByUsername.getSalt()),
                                        this.getName());
        }
        return null;
    }
}
  • Write controller
@Controller
@RequestMapping("/user")
public class UserController {
	@Autowired
    private UserService userService;
    @RequestMapping("/register")
    public String register(User user){
        try {
            userService.register(user);
            return "redirect;/login.jsp";
        }catch (Exception e){
            return "redirect:/register.jsp";
        }
    }
	//Log out. shiro leaves a cache for logged in users if they do not exit
	@RequestMapping("/logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "redirect:/login.jsp";
    }
    //The submission path of the front-end page form is ${pageContext.request.contextPath}/user/login
    @RequestMapping("/login")
    public String login(String username,String password){
        //1. Get the subject object (we have configured DefaultWebSecurityManager in shiro configuration class, and this bean will be automatically injected into the security tool class here)
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(new UsernamePasswordToken(username,password));
            return "redirect:/index.jsp";
        }
        catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("user name does not exist");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("Voucher information(password)error");
        }
        catch(Exception e){
            e.printStackTrace();
        }
        return "redirect:/login.jsp";
    }
}
  • Write business layer
@Service
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    /**
     *During user registration, MD5 + salt + hash is performed for passwords
     * @param user Registered user information
     * @return void
     */
    @Override
    public void register(User user) {
        //The formation method of salt is omitted here
        String salt = "^R$^";
        user.setSalt(salt);
        Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);
        user.setPassword(md5Hash.toHex());
        userMapper.saveUser(user);
    }
    @Override
    public User findUserByUsername(String username) {
        return userMapper.findUserByUsername(username);
    }
}
  • Write the persistence layer and the corresponding mapping configuration file
@Mapper
public interface UserMapper {
    void saveUser(User user);
    User findUserByUsername(String username);
}

shiro provides a set of tags for access control of resources on jsp pages
Role based: for example, some resources can be displayed only when the principal has the identity of administrator. In this case, the process of authorization operation is to set the role of the principal in the realm, and then set which roles the resource is visible to and invisible to in the jsp page, so as to realize the authorization operation
Resource based: Based on the permission string, set the permission string for the principal in the realm. In the front-end page, use the label to set that some resources can only be accessed with a specific permission string
Database table design for authorization operation: user table and role table are designed respectively based on the mode of "user role permission resource"

Keywords: Java Database

Added by urb on Sun, 19 Sep 2021 20:59:49 +0300