Spring Security Authentication, Authorization and Privilege Control

Keywords: Thymeleaf Spring Shiro github

I wrote an article about Shiro, explaining the basic usage of Shiro. I wanted to do a series, but I was busy doing something else, and I never did it. Because of the project requirements, I took time to learn Security, share some experience and learn it together.

This programme logo

Build a simple Web project to implement the following functions

  1. Simple Authentication and Authorization Functions
  2. Privilege control (URL matching privilege control, method privilege control, page button privilege based on Thymeleaf)

Demo Technology Selection

SpringBoot 2.1.6.RELEASE + Spring Security + JdbcTemplate + Thymeleaf

How Security works

Pictures. png

Like Shiro, Security is integrated into our project as a filter to help us manage user privileges. This article does not do too much analysis of the principle, follow-up if there is a sequel for you to explain.

Integrated Security

Add dependencies on SpringBook
build.gradle

dependencies {
    ...
    compile("org.springframework.boot:spring-boot-starter-security")
    ...
}

pom.xml

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

But it's not enough. We need to do something to meet our needs.

Simple authentication and authorization

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user =
             User.withDefaultPasswordEncoder()
                .username("user")
                .password("123456")
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(user);
    }
}

@ Enable Web Security enables Spring Security's support for the Web and integrates with Spring MVC, without any concern for the intermediate process.

Look at the above code again. What have we done?

Know Shiro's friends, see the above code is not very familiar?

  • The first is the URL configuration. We configure "/" and "home" without any access rights. The rest of the requests need authorization, and we configure login and logout.

  • We also need to provide an implementation of User Details Service to provide user authentication and authorization. The above code provides a simple memory-based User Details Manager. In actual development, we can implement a User Details Service ourselves, as will be shown below.

Security will use UsernamePassword Authentication Filter and Logout Filter to process our login and logout requests. The default login and logout paths are "/ login" and "logout" and we only provide user authentication and authorization information.

At this time, in order to make it easier for us to see the effect, add a few more pages.

  • home.html, visible to all
    src/main/resources/templates/home.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Spring Security Example</title>
    </head>
    <body>
        <h1>Welcome!</h1>

        <p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
    </body>
</html>
  • hello.html, depending on the configuration above, this page is accessible only after authentication is required.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Hello World!</title>
    </head>
    <body>
        <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
        <form th:action="@{/logout}" method="post">
            <input type="submit" value="Sign Out"/>
        </form>
    </body>
</html>
  • login.html
    In fact, Security provides us with a login page by default, but to impress us, here we write one ourselves.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
    Invalid username and password.
</div>
<div th:if="${param.logout}">
    You have been logged out.
</div>
<form th:action="@{/login}" method="post">
    <div><label> User Name : <input type="text" name="username"/> </label></div>
    <div><label> Password: <input type="password" name="password"/> </label></div>
    <div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>
  • Finally, configure the mapping of the URL and template
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home").setViewName("home");
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/hello").setViewName("hello");
        registry.addViewController("/login").setViewName("login");
    }

}

OK is here. Simple authentication, authorization, and cancellation are done.

By default, Security logins and redirects to the URL of the previous request

The source code of the above case:
https://github.com/TavenYin/security-example/tree/master/simple-example

About User Details Service

The InMemory User Details Manager used in the above case is not available for actual development. When using Shiro, we usually pull the user's authentication and authorization information from the database. Security is the same.

We implement a User Details Service ourselves. I'm lazy about privileges and roles, and I don't pull from the database.

public class MyUserDetailsService implements UserDetailsService {
    private static final String ROLE_PREFIX = "ROLE_";
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserDO user = null;
        try {
            user = jdbcTemplate.queryForObject(Sql.loadUserByUsername, Sql.newParams(username), new BeanPropertyRowMapper<>(UserDO.class));
        } catch (DataAccessException e) {
            e.printStackTrace();
        }

        if (user == null)
            throw new UsernameNotFoundException("User does not exist:" + username);

        return User.builder()
                .username(username)
                .password(user.getPassword()) // The password here is encrypted
                .authorities(
                        ROLE_PREFIX +"super_admin",
                        ROLE_PREFIX +"user",
                        "sys:user:add", "sys:user:edit", "sys:user:del",
                        "sys:match", "sys:mm"
                )// Here I'm lazy about writing down a few permissions and roles.
                .build();
    }

}

Register our new User Details Service into Security and specify the encryption rules for passwords

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new MyUserDetailsService();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
    }

}

Roles and privileges are separated in Shiro, but are stored in a collection in Security, where roles begin with ROLE_

Privilege control

When the user has completed the authentication and authorization, usually our interface has its own rights. Only when the user's rights match, it can be accessed.

  • Set permissions on matching URL s
    The configuration method above is similar to Shiro's configuration method.
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                // Test Configuration URL Permissions
                .antMatchers("/match/**").hasAuthority("sys:match")
                // Adding multiple permissions to a URL allows multiple configurations
                .antMatchers("/match/**").hasAuthority("sys:mm")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll()
                .and()
        ;
    }
  • Method permissions (available for interface permissions)
    Enable annotations through @Enable Global Method Security
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // Enable @PreAuthorize
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}

Adding annotations to methods that require control rights, @PreAuthorize can also use its own methods for validation

    @PreAuthorize("hasRole('user')")
    public Object user() {
        User user = SecurityUtils.currentUser();
        return "OK, u can see it. " + user.getUsername();
    }

Security also supports other method permission annotations. Interested students can take a look at the @EnableGlobalMethodSecurity class.

  • Page permission control
    Adding dependencies
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<div sec:authorize="hasRole('super_admin')">
    super_admin So
</div>

<div sec:authorize="hasAuthority('sys:mm')">
    sys:mm So
</div>

For more usage reference: https://github.com/thymeleaf/thymeleaf-extras-springsecurity

The privilege test has written a little demo, but it's a bit messy. If you want to refer to it, you can have a look.
https://github.com/TavenYin/taven-springboot-learning/tree/master/spring-security

Reference material

https://spring.io/guides/gs/securing-web/
https://spring.io/guides/topicals/spring-security-architecture/

The next article is planned to bring you a custom extension of Security authentication and cancellation.

Posted by Evoke on Thu, 08 Aug 2019 23:35:31 -0700