I heard that wechat search “Java fish” will change strong!

This article is in Java Server, which contains my complete series of Java articles, can be read for study or interview

(1) Overview

Spring Security is a powerful and highly customizable authentication and access control framework that does two main things: authentication and authorization. I’ve written about SpringSecurity before, but that was just a mock data-based case study. In this installment, I’ll cover authentication and authorization implementations based on real data.

(II) Preliminary project construction

To better demonstrate SpringSecurity, let’s build a simple Web project. Introduce the Thymeleaf dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
Copy the code

Create a new landing page, a home page, and several display pages of different levels: login.html

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Landing page</title>
</head>
<body>
<div>
    <form>
        <h2>Landing page</h2>
        <input type="text" id="username" placeholder="username">
        <input type="password" id="password" placeholder="password">
        <button type="button">landing</button>
    </form>
</div>
</body>
</html>
Copy the code

index.html

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home page</title>
</head>
<body>
<div>
    <h2>Home page</h2>
    <a href="/login">landing</a>
    <div style="overflow: hidden">
        <div style="float: left; margin-left: 20px">
            <h3>level1</h3>
            <a href="/level1/1">level-1-1</a>
            <hr>
            <a href="/level1/2">level-1-2</a>
        </div>
        <div style="float: left; margin-left: 20px">
            <h3>level2</h3>
            <a href="/level2/1">level-2-1</a>
            <hr>
            <a href="/level2/2">level-2-2</a>
        </div>
        <div style="float: left; margin-left: 20px">
            <h3>level3</h3>
            <a href="/level3/1">level-3-1</a>
            <hr>
            <a href="/level3/2">level-3-2</a>
        </div>
    </div>
</div>
</body>
</html>
Copy the code

There are also several pages with different levels

Write your corresponding number in the body.

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
level-1-1
</body>
</html>
Copy the code

Finally, write a controller to receive the request:

@Controller
public class RouteController {

    @RequestMapping({"/","/index"})
    public String index(a){
        return "index";
    }

    @RequestMapping("/login")
    public String toLogin(a){
        return "login";
    }

    @RequestMapping("/level1/{id}")
    public String level1(@PathVariable("id")String id){
        return "level1/"+id;
    }
    @RequestMapping("/level2/{id}")
    public String level2(@PathVariable("id")String id){
        return "level2/"+id;
    }
    @RequestMapping("/level3/{id}")
    public String level3(@PathVariable("id")String id){
        return "level3/"+id; }}Copy the code

The final effect is as follows:

The final realization of different levels of the level page according to different permissions to jump.

The background is implemented based on Mybatis and Mysql database, so we need to introduce Mybatis dependencies in addition to SpringSecurity:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId>  </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.13.</version>
</dependency>
Copy the code

Add data source information to the configuration file and Mybatis configuration:

spring.datasource.url=jdbc:mysql://localhost:3306/security? serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis.mapper-locations=classpath:mapper/*.xml
Copy the code

(3) Realization of authentication and authorization

3.1 Table structure design

Authentication and authorization should be divided into two tables in table design. One table stores user information, including passwords, and the other table stores authorization information. Another table is required to establish the association between users and authorization and give the final table structure:

CREATE TABLE `roles` (
  `id` int(4) NOT NULL,
  `rolename` varchar(255) DEFAULT NULL.PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `sysuser` (
  `id` int(4) NOT NULL,
  `username` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL.PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `user_role` (
  `id` int(4) NOT NULL,
  `user_id` int(4) DEFAULT NULL,
  `role_id` int(4) DEFAULT NULL.PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Copy the code

The following is the entity class, Mapper interface and XML file for these three tables, you can not look at the code, mainly implement a user by the name of the user and related permissions operation:

@Data
public class Roles {
    private Integer id;
    private String roleName;
}

@Data
public class SysUser {
    private Integer id;
    private String userName;
    private String password;
    private List<Roles> roles;
}
Copy the code

Mapper interfaces:

public interface UserMapper {
    public SysUser getUserByUserName(@Param("userName") String userName);
}
Copy the code

XML implementation:


      
<! DOCTYPEmapper PUBLIC "- / / mybatis.org//DTD Mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.javayz.springsecurity.mapper.UserMapper">
    <resultMap id="userMap" type="com.javayz.springsecurity.entity.SysUser">
        <id property="id" column="ID"/>
        <result property="userName" column="username"/>
        <result property="password" column="password"/>
        <collection property="roles" ofType="com.javayz.springsecurity.entity.Roles">
            <result column="name" property="roleName"/>
        </collection>
    </resultMap>
    <select id="getUserByUserName" parameterType="String"  resultMap="userMap">
        select sysuser.*,roles.rolename
        from sysuser
        LEFT JOIN user_role on sysuser.id= user_role.user_id
        LEFT JOIN roles on user_role.role_id=roles.id
        where username= #{userName}
    </select>
</mapper>
Copy the code

3.2 Authentication Process

SpringSecurity’s authentication process starts by finding the user in a database using a username or other unique ID, and storing the user’s password in asymmetric encryption. After getting the user, encrypt the password from the foreground and compare it with the encrypted field in the database to pass authentication.

The first step in this process is to find the user by user name through the Service Service, and this Service Service needs to inherit the UserDetailsService interface in SpringSecurity. This interface returns a User object for SpringSecurity.

@Service
public class UserService implements UserDetailsService {

    @Resource
    private UserMapper userMapper;
    // Find the corresponding user information based on the user name
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        SysUser sysUser = userMapper.getUserByUserName(s);
        if(sysUser! =null){
            List<GrantedAuthority> roles=new ArrayList<>();
            sysUser.getRoles().stream().forEach(x->{
                roles.add(new SimpleGrantedAuthority(x.getRoleName()));
            });
            return new User(sysUser.getUserName(),sysUser.getPassword(),roles);
        }
        throw new UsernameNotFoundException("User not found"); }}Copy the code

3.3 Security Interception Configuration

If the authentication is passed to the userService object, it will automatically compare the password fetched from the database with the password passed from the front end. The ROLES collection is also passed in to the userService, assigning different permissions to different pages in the authorization section.

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;
    / / authorization
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // Only authorized users can access the level page
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");
        // No permissions default to jump to the login page, redirected to /login by default
        http.formLogin();
    }

    / / certification
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(newBCryptPasswordEncoder()); }}Copy the code

3.4 Other points

The password encryption method I use for authentication is BCryptPasswordEncoder, so the password stored in the database also needs to be encrypted. The common way is to encrypt the password in the same way during registration and store it in the database:

String password="xxx";
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encode=bCryptPasswordEncoder.encode(password);
Copy the code

(4) Summary

SpringSecurity is very powerful and supports integration with JWT, Oauth2, and more in addition to this approach. I will continue to update, I am fish boy, we will see you next time.