Application principle analysis of spring security framework

Time:2021-10-16

Introduction to spring security

Background analysis

Data is the most important resource in an enterprise. For these data, some can be accessed directly and anonymously, some can only be accessed after logging in, and some can’t be accessed after you log in successfully. In short, these rules are a means to protect system resources from damage. Almost every system needs such measures to protect data (resources) Protection. We usually design and implement such businesses through software technology. In the early stage, there was no unified standard, and each system had its own independent design and implementation, but it was a common feature for this business. In the subsequent market, specific implementation was made based on sharing, such as spring security and the birth of Apache Shiro

Authentication authorization analysis

When users access resources, the system is required to control users’ permissions. The specific process is shown in the figure:

在这里插入图片描述

Spring Security Overview

Spring security is an enterprise level security framework officially launched by spring. It encapsulates the authentication, authorization, encryption and other functions in the software system, and greatly simplifies the configuration after the introduction of springboot technology. The security control under the current distributed architecture in the market is gradually turning to spring security

Spring security infrastructure

When spring security implements authentication and authorization business in the enterprise, a large number of filters are built at the bottom
在这里插入图片描述
Of which:
The green part is the authentication filter, we need to configure it ourselves, or we can also configure an authentication filter. We can also use the default authentication filter provided by Spring Security. The yellow part is the authorized filter.Spring Security is through these filters and then calling the related objects to complete the authentication and authorization operation.

Spring security quick start

Create project

在这里插入图片描述

Add project dependency


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.3.2.RELEASE</version>
    </parent>

    <groupId>com.cy</groupId>
    <artifactId>02-jt-spring-security</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
</project>

create profile

Create the application.yml file in the resources directory and specify the service port


server:
   port: 8080

Create project startup class


package com.cy.jt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringSecurityApplication {
    public static void main(String[] args) {
        SpringApplication.run(
                SpringSecurityApplication.class,
                args);
    }
}

Run startup class access test

Step 1: check whether the control output automatically generates a password, for example:


Using generated security password: 360123aa-df93-4cd9-bab4-5212af421d2c

Step 2: open the browser for input http://localhost:8080 , and then present the login page, for example:

在这里插入图片描述

In the login window, enter the user name user (system default) and password (the default output of the console when the service is started)
Password), and then click sign in to log in. After successful login, the following interface will appear by default:

在这里插入图片描述

Define login success page

Create a static directory under the resources directory of the project, and create an index.html file in this directory, for example:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <h1>Login Ok</h1>
</body>
</html>

Start the service and conduct the login access test again. After successful login, the system will jump to the index.html page by default, such as

在这里插入图片描述

Configure login password

The first step is to write a method (which can be called in the startup class) to encrypt a name, for example:

static void encodePwd(){
        BCryptPasswordEncoder encoder=new BCryptPasswordEncoder();
        String password="123456";// Plaintext
        String newPwd=encoder.encode("123456");
        System.out.println(newPwd);//$2a$10$fahHJIe3SJm3KcyiPPQ2d.a2qR029gB3qKHrKanQ87u.KbtZ6Phr.
    }

Step 2: configure the user and password in the application.yml file of the springboot project, for example:

spring:
  security:
    user:
      name: jack
      #Password: 123456 # this way, the password is too simple
      password: '{bcrypt}$2a$10$fahHJIe3SJm3KcyiPPQ2d.a2qR029gB3qKHrKanQ87u.KbtZ6Phr.'

Where, {bcrypt} specifies the algorithm used for password encryption

Step 3: start the service and restart the login test

Spring security authentication logic implementation

Custom login logic

Spring security supports the definition of user information (account, password, role, etc.) through configuration files, but this method has obvious disadvantages, that is, it is troublesome to change user information after the system is online. Therefore, spring security also supports providing user authentication and authorization information by implementing the userdetailsservice interface. Its application process is as follows:
Step 1: define the security configuration class, for example:

/**
 *The class described by the @ configuration annotation is the configuration class in spring. The configuration class will be displayed in spring
 *When the project starts, it is loaded first, and the initial configuration of third-party resources is usually carried out in the configuration class
 */
@Configuration
public class SecurityConfig {
    /**
     *Define the spring security password encryption object
     *The @ bean annotation usually describes methods in the class described by the @ configuration annotation,
     *It is used to tell the spring framework that the return value of this method will be handed over to spring management and spring
     *The managed object has a default name, which is the same as the method name. Of course, it can also be
     *@ bean annotation name
     */
    @Bean // the object name is assumed to be the method name by default
    //@Bean ("bcryptpasswordencoder") // the bean object name is bcryptpasswordencoder
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

Step 2: define the userdetailservice interface implementation class and customize the login logic. The code is as follows:
Userdetailservice is the login logic processing object officially provided by spring security. We can implement this interface ourselves, and then write the login logic in the corresponding method

package com.cy.jt.security.service;
@Service
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    /**
     *When we execute the login operation, the bottom layer will call this method through objects such as filters
     *@ param username this parameter is the user name output from the page
     *@ return is generally the user information queried from the database based on the user name
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        //1. Query user information from database based on user name
        //User user=userMapper.selectUserByUsername(username);
        If (! "Jack". Equals (username)) // suppose this is the information queried from the database
            throw new UsernameNotFoundException("user not exists");
        //2. Encapsulate the user information into the userdetails object and return it
        //Suppose the password is queried from the database
        String encodedPwd=passwordEncoder.encode("123456");
        //Suppose this permission information is also queried from the database
        //If the method of assigning permissions is role, use "role_" as prefix when writing string
        List<GrantedAuthority> grantedAuthorities =
                AuthorityUtils.commaSeparatedStringToAuthorityList(
                "ROLE_admin,ROLE_normal,sys:res:retrieve,sys:res:create");
        //This user is the implementation of the userdetails interface provided by spring security, which is used to encapsulate user information
        //Later, we can also build our own implementation of userdetails interface based on our needs
        User user=new User(username,encodedPwd,grantedAuthorities);
        return user;
    }
}

Note: the user object here will be handed over to the spring security framework, which will extract the password information, and then match and verify the password entered by the user

Step 3: start the service for login and access test.

在这里插入图片描述

在这里插入图片描述

Custom landing page

Step 1: define the login page (just create it directly under the static directory). The key codes are as follows:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <title>Please sign in</title>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
<body>
<div>
    <form method="post" action="/login">
        <h2>Please sign in</h2>
        <p>
            <label for="username">Username</label>
            <input type="text" name="username" placeholder="Username" required autofocus>
        </p>
        <p>
            <label for="password">Password</label>
            <input type="password" name="password" placeholder="Password" required>
        </p>
        <input name="_csrf" type="hidden" value="cc1471a5-3246-43ff-bef7-31d714273899" />
        <button type="submit">Sign in</button>
    </form>
</div>
</body>
</html>

Note: the URL of the request is temporarily “/ login”, the request method must be post, and the request parameters must be username and password temporarily. These rules are defined in usernamepasswordauthenticationfilter by default.

Step 2: modify the security configuration class to implement the interface, rewrite the relevant config methods, and perform the login design. The code is as follows:

@Configuration
public class SecutiryConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //super.configure(http);
        //Turn off cross domain attacks. If not, it is easy to make mistakes
        http.csrf().disable();
        //Custom login form
        http.formLogin()
                //Set login page
                .loginPage("/login.html")
                //Set the login request processing address (corresponding to the action in the form), and the userdetailservice object will be accessed during login
                .loginProcessingUrl("/login")
                //Set the request user name parameter to username (the default is username, which can be modified by yourself and needs to be synchronized with the form)
                .usernameParameter("username")
                //The request password parameter is password (the default is password, which can be modified by yourself and needs to be synchronized with the form)
                .passwordParameter("password")
                //Set login success jump page (default is / index. HTML)
                .defaultSuccessUrl("/index.html")
                //Login failed page (default is / login. HTML? Error)
                .failureUrl("/login.html?error");
        //Certification design
        http.authorizeRequests()
                //Set consultation to release
                .antMatchers("/login.html").permitAll()
                //Set the request to be authenticated (except for the above to be released, all others shall be authenticated)
                .anyRequest().authenticated();
    }
}

Login success and failure processor

Many systems now adopt the front-end and back-end separation design. After successful login, we may jump to an address of the front-end system or return a JSON data. We can define the processing operations for successful login, for example:

Define login success processor:

Scenario 1: a processor that can directly perform redirection, such as

package com.cy.jt.auth.config.authentication;
public class RedirectAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
//Define the URL to jump to
    private String redirectUrl;
    public RedirectAuthenticationSuccessHandler(String redirectUrl){
        this.redirectUrl=redirectUrl;
    }
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,
                            Authentication authentication)
            throws IOException, ServletException {
            httpServletResponse.sendRedirect(redirectUrl);
    }
}

Scheme 2: the processor that can directly return JSON data, for example:

package com.cy.jt.security.config.handler;
/**Processing login failed
 *0) default - default
 *1) authentication authentication
 *2) failure failure
 *3) handler processor
 * */
public class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(
            HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse,
            AuthenticationException e) throws IOException, ServletException {
        //1. Set the code of response data
        httpServletResponse.setCharacterEncoding("utf-8");
        //2. Tell the client the type of response data and how the client displays it
        httpServletResponse.setContentType("application/json;charset=utf-8");
        //3. Get an output stream object
        PrintWriter out=httpServletResponse.getWriter();
        //4. Output a JSON format string to the client
        //4.1 build a map object
        Map<String,Object> map=new HashMap<>();
        map.put("state","500");
        map.put("msg","username or password error");
        //4.2 convert an object into JSON format string based on objectmapper object in Jackson
        String jsonStr= new ObjectMapper().writeValueAsString(map);
        out.println(jsonStr);
        out.flush();
    }
}

Define login failure processor:

Scenario 1: login failure redirects to the page, for example


package com.cy.jt.auth.config.authentication;
public class RedirectAuthenticationFailureSuccessHandler implements AuthenticationFailureHandler {
    private String redirectUrl;
    public RedirectAuthenticationFailureSuccessHandler(String redirectUrl){
        this.redirectUrl=redirectUrl;
    }
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.sendRedirect(redirectUrl);
    }
}

Scheme 2: define the login failure processor, for example:

package com.cy.jt.security.config.handler;

/**Processing login failed
 *0) default - default
 *1) authentication authentication
 *2) failure failure
 *3) handler processor
 * */
public class DefaultAuthenticationFailureHandler
         implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(
            HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse,
            AuthenticationException e) throws IOException, ServletException {
        //1. Set the code of response data
        httpServletResponse.setCharacterEncoding("utf-8");
        //2. Tell the client the type of response data and how the client displays it
        httpServletResponse.setContentType("application/json;charset=utf-8");
        //3. Get an output stream object
        PrintWriter out=httpServletResponse.getWriter();
        //4. Output a JSON format string to the client
        //4.1 build a map object
        Map<String,Object> map=new HashMap<>();
        map.put("state","500");
        map.put("msg","username or password error");
        //4.2 convert an object into JSON format string based on objectmapper object in Jackson
        String jsonStr= new ObjectMapper().writeValueAsString(map);
        out.println(jsonStr);
        out.flush();
    }
}

Modify the configuration class and set the login success and failure processor.

@Configuration
public class SecutiryConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //super.configure(http);
        //Turn off cross domain attacks. If not, it is easy to make mistakes
        http.csrf().disable();
        //Custom login form
        http.formLogin()
                //Set login page
                .loginPage("/login.html")
                //Set the login request processing address (corresponding to the action in the form), and the userdetailservice object will be accessed during login
                .loginProcessingUrl("/login")
                //Set the request user name parameter to username (the default is username, which can be modified by yourself and needs to be synchronized with the form)
                .usernameParameter("username")
                //The request password parameter is password (the default is password, which can be modified by yourself and needs to be synchronized with the form)
                .passwordParameter("password")
                //Set login success jump page (default is / index. HTML)
                . successhandler (New redirectauthenticationsuccesshandler ("your URL")
                //Login failed page (default is / login. HTML? Error)
               . failurehandler (New redirectauthenticationfailurehandler ("your URL"))
        //Certification design
                http.authorizeRequests()
                //Set consultation to release
                .antMatchers("/login.html").permitAll()
                //Set the request to be authenticated (except for the above to be released, all others shall be authenticated)
               .anyRequest().authenticated();
    }
}

Step 4: start the service for access test (test with correct and wrong accounts respectively).

Release static resources

In the configure (httpsecurity HTTP) method in the securitymanager configuration class, we can define the static resources to be released through the anmatchers method, for example:

. authorizerequests() // set the authorization of the request
        . antmatchers (// configure the authorization of the following path
                "/index.html",
                "/js/*",
                "/css/*",
                "/img/**",
                "/bower_components/**",
                "/login.html"
        ). permitall() // set all the above paths to access (release) without login

Of which:

  • “*” is used to match 0 or more characters
  • “* *” is used to match 0 or more directories and characters

Design and implementation of logout

In the configure (httpsecurity HTTP) method in the securitymanager configuration class, add the logout configuration, such as

Http. Logout() // start setting logout information
        . logouturl ("/ logout") // logout path
        .logoutSuccessUrl("/login.html?logout");// Set the page displayed after logout

Spring security authorization logic implementation

Modify authorization configuration class

Add the enable global method access control annotation on the permission configuration class, for example:

package com.cy.auth.config;
//This configuration class configures spring security,
//Prepostenabled = true indicates that the permission management function is started
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SpringSecurityConfigurer extends WebSecurityConfigurerAdapter {
    ……
}

Define resource controller

Define a resourcecontroller class as a resource access object, such as


package com.cy.jt.auth.controller;

@RestController
public class ResourceController {
    @PreAuthorize("hasAuthority('sys:res:create')")
    @RequestMapping("/doCreate")
    public String doCreate(){
        return "add resource";
    }
    @PreAuthorize("hasAuthority('sys:res:update')")
    @RequestMapping("doUpdate")
    public String doUpdate(){
        return "update resource";
    }
    @PreAuthorize("hasAuthority('sys:res:delete')")
    @RequestMapping("/doDelete")
    public String doDelete(){
        return "delete resource";
    }
    @PreAuthorize("hasAuthority('sys:res:retrieve')")
    @RequestMapping("/doRetrieve")
    public String doRetrieve(){
        return "retrieve resource";
    }
}

When the @ preauthorize annotation describes a method, it is used to tell the system that permission detection is required when accessing this method. You need to have specified permissions to access. For example:

  • @Preauthorize (“hasauthority (‘sys: res: Delete ‘) requires sys: res: delete permission
  • @Preauthorize (“hasrole (‘admin ‘)) needs to have the admin role to start the service access test

Log in with different users and then perform resource access. If you do not have permission, will you see the response status 403, as shown in the figure:

在这里插入图片描述

Spring authentication and authorization exception handling

Exception type

For the spring security framework, the following two types of exceptions may occur when implementing authentication and authorization services:
1) Authenticationexception (an exception that may occur when a user accesses a method that requires authentication before authentication. This exception usually corresponds to the status code 401)
2) Accessdeniedexception (an exception that may occur when accessing some resources without permission after user authentication. Does this exception usually correspond to 403)

Exception handling specification

The spring security framework gives the default exception handling method. When the default exception handling method does not meet our actual business needs, we need to define our own exception handling logic. When writing the logic, we need to follow the following specifications:
1) Authenticationentrypoint: handles authenticationexception exceptions uniformly
2) Accessdeniedhandler: uniformly handle accessdeniedexception exceptions

Custom exception handling object

Handling access exceptions without authentication

package com.cy.jt.config;
public class DefaultAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException e) throws IOException, ServletException {
        //Set the encoding of the response data
        response.setCharacterEncoding("utf-8");
        //Tell the browser the type of content to respond to and the encoding
        response.setContentType("application/json;charset=utf-8");
        Map<String,Object> map=new HashMap<>();
        map.put("state",401);
        Map.put ("message", "please log in first");
        PrintWriter out=response.getWriter();
        out.println(new ObjectMapper().writeValueAsString(map));
        out.flush();
        out.close();
    }
}

Handle the exception thrown when there is no permission

package com.cy.jt.config;

public class DefaultAccessDeniedExceptionHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request,
                       HttpServletResponse response,
                       AccessDeniedException e) throws IOException, ServletException {
        //Set the encoding of the response data
        response.setCharacterEncoding("utf-8");
        //Tell the browser the type of content to respond to and the encoding
        response.setContentType("application/json;charset=utf-8");
        Map<String,Object> map=new HashMap<>();
        map.put("state",403);
        Map.put ("message", "no access to this resource");
        PrintWriter out=response.getWriter();
        out.println(new ObjectMapper().writeValueAsString(map));
        out.flush();
        out.close();
    }
}

Configuring exception handling objects

Add a custom exception handling object in the configuration class securityconfig. The code is as follows


 http.exceptionHandling()
            .authenticationEntryPoint(new DefaultAuthenticationEntryPoint())
            .accessDeniedHandler(new DefaultAccessDeniedExceptionHandler());

After configuration, restart the service for access test analysis

System session state analysis and practice what is session state

The state information (similar to meeting minutes) generated during the communication between the client and the server is called session state

How is session state stored

The HTTP protocol is used when the client browser communicates with the server. This protocol itself is a stateless protocol, that is, the session state cannot be stored through this protocol. At this time, a cookie and session method is used between the server and the client to record the session state

Analysis of stateful conversation Technology

  • Cookie Technology

Cookie is an object created by the server but storing the session state on the client. This object is divided into two types: session cookie and persistent cookie. When accessing a specific domain name, the browser will carry the valid cookie of this domain to the server

  1. Session Cookie: the browser closes and the cookie life cycle ends (generally, the default is session cookie)
  2. Persistent Cookie: a persistent cookie specifies the life cycle when the cookie object is created, such as one week. Even if the browser is closed, the persistent cookie is still valid
  • Session Technology

The session technology is created by the server and stores an object of session state on the server. When the session object is created, a session cookie object will be created, and the sessionid will be written to the client through the session cookie. The client will carry this session cookie when accessing the server next time and find the session object through jsessionid, The default life cycle of a cookie is 30 minutes

To obtain the user’s authentication information in spring security, you can implement it in the following ways:


 Authentication authentication =
                SecurityContextHolder.getContext().getAuthentication();

Analysis of stateless session Technology

For stateful session implementation, there may be many problems in the distributed architecture. For example, the browser does not support carrying cookie information of other domains for resource access by default, and the session on the server cannot be shared by default. Of course, we have a way to persist the session to some databases, such as redis. The next time we request to other servers (such as Tomcat), The login information can be obtained directly from redis, but if the concurrency is large, the access pressure of the database will increase sharply. Too much pressure may lead to system downtime. Therefore, another scheme is to store the user’s login status information on the client. The server does not record any status, and the server is only responsible for parsing the status information transmitted by the client, Based on this method, the user login status is judged. Such a session process is called stateless session

Summary

Analysis of key and difficult points

  • What is the background of spring security?
  • Spring security quick start? (dependency, configuration, login authentication, password encryption – start generation, configuration file)
  • Spring security authentication logic analysis and practice? (authentication method – user name and password, login page)
  • Face, securityconfig.userservicedetail, success, failure, release)
  • Spring security authorization logic analysis and implementation? (why, authorization steps, notes used)

FAQ analysis

  • How to understand certification? (determine the legitimacy of user identity)
  • How to verify the legitimacy of user identity? (user password, fingerprint, face brushing, ID card brushing,…)
  • How to authenticate? (write your own authentication logic and use the framework to write authentication logic – respect the framework rules)
  • What are the certification and authorization frameworks in the market? (SpringSecurity,Shiro)
  • Why spring security? (powerful. After the birth of springboot, the configuration has been greatly simplified)
  • What encryption method do you use in spring security? (bcrypt, the bottom layer hash irreversible encryption of password based on random salt method, which is more secure, and the defect is slow)
  • What APIs have you used in spring security? (BcryptPasswordEncoder,UserDetailService,UserDetail,User,
  • AuthenticationSuccessHandler,AuthenticationFailureHandler,…)
  • Why do you want permission control? (prevent illegal users from destroying data)
  • Steps for permission control in spring security (@ enableglobalmethodsecurity, @ preauthorize)
  • What exceptions might spring security encounter during authentication and authorization?
  • How to handle the exceptions when spring security accesses authorized resources without authentication and authorization?
  • Job: after the user logs in successfully, where does the user information exist by default? (Session)
  • Job: how to get the user information after the user logs in successfully? (user name and permission of this user)

Bug analysis

  • Dependent download incomplete
  • File download in response to JSON data

This is the end of this article about the application of spring security framework. For more information about spring security framework, please search the previous articles of developeppaer or continue to browse the relevant articles below. I hope you will support developeppaer in the future!

Recommended Today

Swift advanced (XV) extension

The extension in swift is somewhat similar to the category in OC Extension can beenumeration、structural morphology、class、agreementAdd new features□ you can add methods, calculation attributes, subscripts, (convenient) initializers, nested types, protocols, etc What extensions can’t do:□ original functions cannot be overwritten□ you cannot add storage attributes or add attribute observers to existing attributes□ cannot add parent […]