Authority verification based on Spring Boot

Keywords: Spring Java Thymeleaf Web Development

Preface:

There is a part of background management system, which needs to divide the roles of users, and then restrict the access content according to the corresponding permissions of roles; or some portal sites, only special personnel can see the corresponding page information. These all need permission control.

 

Technical points:

  • Spring Boot+Spring Security

 

Project dependency:

<!--Contain spring-security-web,config,core-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!--web Development related-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

 

Code:

Entity class:

package com.menghao.security.model.entity;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;

/**
 * <p>System user entity class. < br >
 *
 * @author menghao.
 * @version 2018/3/23.
 */
@Data
@Entity
@Table(name = "system_user")
public class SystemUser implements Serializable {

    @Id
    @GeneratedValue
    private Long id;

    private String username;

    private String password;

    private String role;
}

Configuration class:

package com.menghao.security.web.config;

import com.menghao.security.model.entity.SystemUser;
import com.menghao.security.service.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>Spring-Security Configuration class. < br >
 *
 * @author menghao.
 * @version 2018/3/22.
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final DataSource dataSource;
    // JPA warehouse (code omitted)
    private final UserRepository userRepository;

    @Autowired
    public WebSecurityConfig(@Qualifier("dataSource") DataSource dataSource, @Qualifier("userRepository") UserRepository userRepository) {
        this.dataSource = dataSource;
        this.userRepository = userRepository;
    }

    /**
     * Define user authentication logic
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // Three choose one.
        // memoryMode(auth);
        // jdbcMode(auth);
        customizeMode(auth);
    }

    /**
     * Define user authorization logic
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // Define login behavior: allow arbitrary access, login success and failure page address
        http.formLogin().loginPage("/login").defaultSuccessUrl("/auth/index").failureUrl("/login").permitAll()
                .and()
                // Define cookies and their time of existence
                .rememberMe().tokenValiditySeconds(-1).key("marvel")
                .and()
                // Define role MANAGER access path
                .authorizeRequests().antMatchers("/manager/**").hasRole("MANAGER")
                .and()
                // Define role USER access path
                .authorizeRequests().antMatchers("/user/**").hasRole("USER")
                .and()
                // Define public access path
                .authorizeRequests().antMatchers("/common/**").hasAnyRole("USER", "MANAGER")
                .and()
                // Define logout behavior
                .logout().logoutUrl("/logout").logoutSuccessUrl("/login").permitAll();
    }

    /**
     * Memory mode
     * inMemoryAuthentication: Add users to memory and specify permissions
     */
    private void memoryMode(AuthenticationManagerBuilder auth) throws Exception {
        // You need to specify after Spring Security5, otherwise exceptions will be thrown
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        auth.inMemoryAuthentication()
                .passwordEncoder(passwordEncoder)
                .withUser("admin").password(passwordEncoder.encode("admin")).roles("MANAGER")
                .and().withUser("user").password(passwordEncoder.encode("user")).roles("USER");
    }

    private static final String QUERY_BY_USERNAME = "select U.username,U.password,true from system_user U where U.username = ?";

    private static final String QUERY_AUTHORITIES_BY_USERNAME = "select U.username,U.role from system_user U where U.username = ?";

    /**
     * JDBC Pattern
     * jdbcAuthentication: The default query is implemented in JdbcDaoImpl
     *
     * @see UserDetails
     */
    private void jdbcMode(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication().dataSource(dataSource)
                // You need to specify after Spring Security5, otherwise exceptions will be thrown
                .passwordEncoder(new BCryptPasswordEncoder())
                // Three fields need to be queried: user name, password, and enable
                .usersByUsernameQuery(QUERY_BY_USERNAME)
                // Two fields need to be queried: user name and permission
                .authoritiesByUsernameQuery(QUERY_AUTHORITIES_BY_USERNAME);
    }

    @Bean
    public UserDetailsService customizeUserDetailsService() {
        // Implementation: loadUserByUsername(String username), return a UserDetails type object
        return username -> {
            SystemUser systemUser = userRepository.findByUsername(username);
            List<GrantedAuthority> authorities = new ArrayList<>();
            authorities.add(new SimpleGrantedAuthority(systemUser.getRole()));
            return new User(systemUser.getUsername(), systemUser.getPassword(), authorities);
        };
    }

    /**
     * Custom mode
     * userDetailsService: Specifies an implementation that returns the UserDetails type
     */
    private void customizeMode(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customizeUserDetailsService())
                .passwordEncoder(new BCryptPasswordEncoder());
    }
}

Controller:

package com.menghao.security.web.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p>Public controller. < br >
 *
 * @author menghao.
 * @version 2018/3/24.
 */
@RestController
@RequestMapping("common")
public class CommonController {

    @RequestMapping("data")
    public String managePage() {
        return "this is common data";
    }
}
package com.menghao.security.web.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p>Administrator controller. < br >
 *
 * @author menghao.
 * @version 2018/3/24.
 */
@RestController
@RequestMapping("manager")
public class ManagerController {

    @RequestMapping("data")
    public String managePage() {
        return "you are a manager";
    }
}
package com.menghao.security.web.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p>Common user controller. < br >
 *
 * @author menghao.
 * @version 2018/3/24.
 */
@RestController
@RequestMapping("user")
public class UserController {

    @RequestMapping("data")
    public String managePage() {
        return "you are an user";
    }
}

Login page:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
    <title>Login page</title>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <script type="application/x-javascript">
        addEventListener("load", function () {
            setTimeout(hideURLbar, 0);
        }, false);
        function hideURLbar() {
            window.scrollTo(0, 1);
        }
    </script>
    <link th:href="@{/css/style.css}" rel='stylesheet' type='text/css'/>
    <script th:src="@{/js/jquery.min.js}"></script>
</head>
<body>
<script>$(document).ready(function (c) {
    $('.close').on('click', function (c) {
        $('.login-form').fadeOut('slow', function (c) {
            $('.login-form').remove();
        });
    });
});
</script>
<h1>Login Form</h1>
<div class="login-form">
    <div class="close"></div>
    <div class="head-info">
        <label class="lbl-1"> </label>
        <label class="lbl-2"> </label>
        <label class="lbl-3"> </label>
    </div>
    <div class="clear"></div>
    <div class="avtar">
        <img th:src="@{/images/avtar.png}"/>
    </div>
    <form th:action="@{/login}" method="post">
        <input type="text" class="text" name="username" placeholder="User name"/>
        <div class="key">
            <input type="password" name="password" placeholder="Password"/>
        </div>
        <div class="signin">
            <input type="submit" value="Login">
        </div>
    </form>
</div>
</body>
</html>

 

Result:

After the USER of role USER logs in, he can visit http://localhost/user/data and http://localhost/common/data, and display the corresponding string; if he visits http://localhost/manager/data, he will return 403 error page.

The reverse is true for users with the role MANAGER.

 

Conclusion:

It's very easy to integrate Spring Boot and Spring Security. If you need more fine-grained page permission control, you need to introduce

<dependency>
   <groupId>org.thymeleaf.extras</groupId>
   <artifactId>thymeleaf-extras-springsecurity4</artifactId>
   <version>2.1.2.RELEASE</version>
</dependency>

In this way, you can use sec:authorize = "hasRole('role'user ') to control the display of page elements.

Posted by Louis-Platt on Wed, 01 Apr 2020 10:38:23 -0700