Spring security permission validation

Keywords: Spring Mybatis Database xml

Now is the third summer vacation, have the opportunity to come to the enterprise to learn something, in contact with a spring boot after the need to do permission verification, just for the login user permission verification, will organize the content and share with you.

See some blog content:
https://blog.csdn.net/UpdateAllTheTime/article/details/82664103
https://www.cnblogs.com/rolandlee/p/9580492.html
Not springboot.security, but the way to control mvc.
Below is the configuration using springboot.security, which is 2.0
https://blog.csdn.net/qq_24434671/article/details/86595157
https://blog.csdn.net/u014174854/article/details/83338804
Big Brother: https://www.iteye.com/blog/412887952-qq-com-2441544

The most detailed information is natural: from the official Document https://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/#wsca

The above blog, some partial theory, practical content involved in the content is not suitable for my current knowledge framework, some are completely without commentary, it is really distressing, the following information collected to collate, get a more practical content.

First, we use the Spring Security section of the spring framework for privilege management.

What is Spring Security?

Based on the security framework of Spring AOP and Servlet filtering, identity authentication and authorization are handled at the Web request level and method invocation level, or dependency injection and aspect-oriented functions are used.
Security mainly includes two operations: authentication (establishing a declared subject for the user, which generally refers to the user, device, system) and authorization (or privilege control).
Spring Security's support for Web security relies heavily on Servlet filters. These filters intercept incoming requests and perform some security processing before the application processes the request.
FilterToBeanProxy is a special Servlet processor that does not do much work by itself, but delegates its work to a Bean in the context of a Spring application. Delegated beans, like other Servlet filters, implement the interface of javax.servlet.Filter. The Bean that FilterToBeanProxy proxies to can be any implementation of javax.servlet.Filter.

Dependencies to be added

web starter and security starter

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Design of database:

The aim is to achieve:
Adding restrictions to users with different identities, users with specified identities can only perform specified operations.
Tables involved: user, role, user_role
The contents of each table:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
  `password` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
  `gender` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci 
CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rolename` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
  `roledesc` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

CREATE TABLE `user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userId` int(11) NOT NULL,
  `roleId` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `userId` (`userId`),
  KEY `roleId` (`roleId`),
  CONSTRAINT `user_role_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `user` (`id`),
  CONSTRAINT `user_role_ibfk_2` FOREIGN KEY (`roleId`) REFERENCES `role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

sql statements used to query the user's corresponding privilege information

select u.username as name, u.password as password, r.rolename as role, r.roledesc as describute
 from user as u, role as r, user_role as ur
 where u.id=ur.userId and ur.roleId = r.id;

For convenience, you can create a view with the sql statement above:

+------------+-------------+------+-----+---------+-------+
| Field      | Type        | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| name       | varchar(20) | YES  |     | NULL    |       |
| password   | varchar(20) | YES  |     | NULL    |       |
| role       | varchar(20) | YES  |     | NULL    |       |
| describute | varchar(50) | YES  |     | NULL    |       |
+------------+-------------+------+-----+---------+-------+

The inserted data is displayed in the view as follows:

+-------+----------+--------+------------+
| name  | password | role   | describute |
+-------+----------+--------+------------+
| admin | admin    | admin  | admin      |
| admin | admin    | normal | normal     |
| user  | 123      | admin  | admin      |
+-------+----------+--------+------------+

Creating springboot Projects

The complete pom.xml file, about the mybayis section, needs to import two dependencies.

<?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>

    <groupId>com.nianzuochen.cn</groupId>
    <artifactId>testsecurity</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <!-- springsecurity starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- spring Of web starter -->
        <!-- Not using built-in tomcat Converted to domestic capital undertow -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>

        <!-- Simplified Entity Class Method Writing -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.22</version>
            <scope>provided</scope>
        </dependency>

        <!-- Database Connection -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>

        <!--
            integration mybatis Need to import mybatis-spring-boot-starter
            and mysql-connector-java
        -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>

        <!-- swagger2 Document automatic generation for testing -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.2.2</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!-- spring Provided plug-ins, can be carried out package,clean etc. -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <!-- Configuration scanner queries to .xml file -->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
</project>

The starter of the database connection has been added, so you need to add a configuration about the database connection:
application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/testsecurity?characterEncoding=utf8&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=root

Write the startup class to start the spring boot project

@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
@EnableSwagger2
@MapperScan("com.nianzuochen.cn.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

idea's console will have an initial password:
Browser input: http://localhost:8080 will get a default user login page:
You can remove this default login authentication box from the startup class:

@SpringBootApplication(exclude = SecurityAutoConfiguration.class)

Next, complete the mybatis content for the database

Some points needing attention are as follows:

1.UserInfo class:

Spring Security uses UserDetail as the login user class, that is, the username and password of the user login are encapsulated in the UserDetail class for subsequent authorization.

/**
 * Store user details, inherit from the UserDetails class, and submit user information to WebSecurity Configurer Adapter
 * web Security configuration adapter, judging the rights of login users through the information provided in this class, and processing the rights
 */

@Getter
@Setter
public class UserInfo implements UserDetails {

    private Long id;
    private String username;
    private String password;
    private Integer gender;
    private List<Role> roles;


    /**
     *  Content of the authority granted by the method of inheritance
     * @return
     */
    @Override
    @JsonIgnore
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (Role role : roles) {
            // Implementing class SimpleGrantedAuthority of GrantedAuthority
            authorities.add(new SimpleGrantedAuthority(role.getRolename()));
        }
        return authorities;
    }

    @Override
    @JsonIgnore
    public String getPassword() {
       return password;
    }

    @Override
    @JsonIgnore
    public String getUsername() {
        return username;
    }

    /**
     * Indicates whether the user's account has expired, set it as not, here you can add custom expired content
     * For example, add the login time to the user information, then set the expiration time and decide whether to let the current login user's account expire.
     * @return
     */
    @Override
    @JsonIgnore
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * Indicates whether the current user is locked
     * @return
     */
    @Override
    @JsonIgnore
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * Determine whether the credentials (passwords) of the current logged-in user are expired and the expired credentials prevent authentication
     * @return
     */
    @Override
    @JsonIgnore
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * Determine whether the current user is enabled
     * @return
     */
    @Override
    @JsonIgnore
    public boolean isEnabled() {
        return true;
    }
}

2.role class:

Role is a parameter of UserInfo, because UserInfo extends UserDetail, and UserDetail implementations Serializable, Role also needs to be serialized.
Also in Spring Security, restricting the user's permission name must start with ROLE_and be careful when entering test cases.

@Getter
@Setter
public class Role implements Serializable {

    private Long id;
    /**
     * rolename The content must start with ROLE_
     */
    private String rolename;
    private String roledesc;
}

3. The corresponding xml file:

Involving multi-table queries

<mapper namespace="com.nianzuochen.cn.mapper.RoleMapper">
    <select id="loadRoleByUserId" resultType="com.nianzuochen.cn.domain.Role">
        select * from role as r, user_role as ur where #{userId} = ur.userId and ur.roleId = r.id;
    </select>
</mapper>
<mapper namespace="com.nianzuochen.cn.mapper.UserInfoMapper">
    <!-- Use mybatis Data to be queried and UserInfo Matching -->
    <select id="loadUserByUsername" resultMap="lazyLoadRoles" >
        select * from user WHERE username=#{username};
    </select>
    <resultMap id="lazyLoadRoles" type="com.nianzuochen.cn.dao.UserInfo">
        <id column="id" property="id"/>
        <id column="username" property="username"/>
        <id column="password" property="password"/>
        <id column="gender" property="gender"/>
        <collection property="roles" ofType="com.nianzuochen.cn.domain.Role"
                    select="com.nianzuochen.cn.mapper.RoleMapper.loadRoleByUserId" column="id">
        </collection>
    </resultMap>
</mapper>

4.UserInfoService class

Spring Security uses UserDetail to restrict user information and Service. Given the parent class UserDetailService, there is only one abstract method to query user information based on user name.

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

The UserInfoServiceImpl class naturally implements this basic method:

@Service
// @ Transactional annotations manage transactions
@Transactional
public class UserInfoServiceImpl implements UserDetailsService {


    @Autowired
    UserInfoMapper userInfoMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserInfo userInfo = userInfoMapper.loadUserByUsername(username);
        if (userInfo == null) {
            throw new UsernameNotFoundException("Incorrect username");
        }

        return userInfo;
    }
}

5. Add a test to the controller:

Use swagger, that is, enter http://localhost:8080/swagger-ui.html in the browser to call swagger and start testing the interface document.

/**
 * Use admin to measure the following
 * The test queries the user's information and ignores the user's username and password in the entity class.
 * {"id":1,"gender":1,"roles":[{"id":1,"rolename":"ROLE_admin","roledesc":"admin"},{"id":2,"rolename":"ROLE_normal","roledesc":"normal"}]}
 * @return
 */
@GetMapping("/user")
public UserInfo getAdmin(String username) {
    return (UserInfo) userInfoService.loadUserByUsername(username);
}

At present, the part of location data acquisition is completed, followed by the content authorized by Spring Security.

The most basic security configuration information is in the WebSecurity ConfigureAdapter class, where there are many security-related configurations:

//Authentication Manager Creator
protected void configure(AuthenticationManagerBuilder auth) 
// Configuration of web Security
public void configure(WebSecurity web)
// Configuration of http security
protected void configure(HttpSecurity http)
......

When matching the data of the current login object, all users'information is naturally imported. After spring security 5.x, users' passwords need to be encrypted. Bcypt: bcrypt is a cross-platform file encryption tool.

To turn on Spring method-level security, use the @Enable Global Method Security annotation

Here is the complete information for this permission validation WebSecurity ConfigureAdapter

/**
 * This class is a configuration class
 */
@Configuration      // Declare as configuration class
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserInfoService userInfoService;

    /**
     * Adding user privileges for login
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        ......
        // The details are broken down below.
    }

    /**
     * Network security, ignoring some ant-style requests, users can not directly access website resources
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        System.out.println("websecurity");
        ......
        // The details are broken down below.
    }

    /**
     * This is the main configuration that restricts http requests, including successful and failed requests and exit processing
     * Various method calls are described in detail in the annotations to the HttpSecurity class
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        System.out.println("HttpSecurity");
        ......
        // The details are broken down below.
    }
}

A total of three methods are override d. The order of invocation of the three methods is:

Adding user privileges
HttpSecurity
websecurity

First

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    System.out.println("Adding user privileges");

    auth.userDetailsService(userInfoService).passwordEncoder(new BCryptPasswordEncoder());
}

The Authentication Manager Builder (Authentication Manager Builder) is set up. The processor used to query the username and password is set to its own processor method, and then the encryption mode is set for the authentication configuration.

Next

/**
 * Network security, ignoring some ant-style requests, users can not directly access website resources
 * @param web
 * @throws Exception
 */
@Override
public void configure(WebSecurity web) throws Exception {
    System.out.println("websecurity");
    web.ignoring().antMatchers("/index.html", "/static/**", "/login_p",
            "/favicon.ico","/swagger-ui.html","/webjars/**","/swagger-resources/**","/v2/**");
}

Last

/**
 * This is the main configuration that restricts http requests, including successful and failed requests and exit processing
 * Various method calls are described in detail in the annotations to the HttpSecurity class
 * @param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    System.out.println("HttpSecurity");

    http.
            authorizeRequests() // Define protected and unprotected URL s
            .and()
            // uri requests that allow login
            .formLogin().loginProcessingUrl("/login")
            // SQL Server Authentication
            .usernameParameter("username").passwordParameter("password")
            // Logon Failure Handling
            .failureHandler(new AuthenticationFailureHandler() {
                @Override
                public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                    httpServletResponse.setContentType("application/json;charset=utf-8");// Setting the encoding format
                    String result = "";

                    System.out.println();


                    // Classification of anomalies
                    if (e instanceof UsernameNotFoundException) {
                        result = "Account name does not exist";
                    } else if (e instanceof LockedException) {
                        result = "Logon failure";
                    } else if (e instanceof BadCredentialsException ){
                        result = "Password error";
                    }

                    // Write login failure information
                    httpServletResponse.setStatus(401);
                    ObjectMapper om = new ObjectMapper();
                    PrintWriter out = httpServletResponse.getWriter();
                    out.write(om.writeValueAsString(result));
                    out.flush();
                    out.close();
                }
            })
            // Logon successful processing
            .successHandler(new AuthenticationSuccessHandler() {
                @Override
                public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                    httpServletResponse.setContentType("application/json;charset=utf-8");

                    // Gets the body of the current login object, which also contains the object's permissions
                    UserInfo userInfo = (UserInfo) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
                    HttpSession session = httpServletRequest.getSession();
                    session.setAttribute("operationUserId", userInfo.getId());
                    ObjectMapper om = new ObjectMapper();
                    PrintWriter out = httpServletResponse.getWriter();

                    // Logon successfully returns logged-in objects
                    out.write(om.writeValueAsString(userInfo));
                    out.flush();
                    out.close();
                }
            })
            .permitAll() // Set up a login address that everyone can access
            .and()
            .logout()
            .logoutUrl("/logout")
            .logoutSuccessHandler(new LogoutSuccessHandler() {
                @Override
                public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                    httpServletResponse.setContentType("application/json;charset=utf-8");
                    String result = "Successful cancellation";
                    ObjectMapper om = new ObjectMapper();
                    PrintWriter out = httpServletResponse.getWriter();
                    out.write(om.writeValueAsString(result));
                    out.flush();
                    out.close();
                }
            })
            .permitAll()
            .and()
            .authorizeRequests()
            .antMatchers("/", "/login", "/logout").permitAll()//Unblocked address
            .anyRequest()//All remaining requests are intercepted
            .authenticated();
    http.csrf().disable();//Cross-station Request Forgery protection, turn off here

}

Here we only deal with login and logout, limit login uri to login and exit uri to layout. These two uri allow everyone to access, deal with login success and logon failure, and deal with logout.

The successful processing of login is to pass the user's details to the front-end, which contains the user's rights (roles). When different users are processed differently, the delivered objects may be more complex, such as the addresses that allow such users to access.
Various processing methods can write a single class for data transfer processing, where the content is small to write together.

Here is the information to complete the administrator's login.

Posted by heminfotech on Thu, 29 Aug 2019 06:26:33 -0700