Implemented authentication function with Spring Security ①

Currently, I am studying the framework and am dealing with Spring Boot. When implementing the login function, it seems to be the standard to use the authentication function of Spring Security, so I implemented it using this. At that time, I had a lot of trouble, and it took me a long time to implement it, so I tried to summarize it myself.

1. Image of Spring Security

SpringBoot_Di_Security_DB.png

Very simple image I'm sorry, but the image looks like this. Using SpringBoot's DI (Dependency Injection) function, ** Spring Security is read from the outside and exchanged with the DB **. I won't explain DI in detail, but I think it's like being able to use the object's functions at any time by putting an external object in a box called a DI container. In this case, that object is "SpringSecurity". In addition, Spring Security has a method to connect to DB, but since Spring Data JDBC is actually responsible, in the above figure, JDBC will be inserted between Spring Security and DB.

2. Implementation flow.

I will implement it at once, but I will briefly explain the flow of implementation.

  1. Display List in MySQL.
  2. Implementation of login screen.
  3. Implementation of Spring Security authentication function.

It looks like this. Since No. 3 is the main and the largest volume this time, it is easier to solve when an error occurs later if you move to No. 3 after surely implementing No. 1 and No. 2. In my case, when I tried to implement No. 3 from the beginning, an error occurred when loading MySQL, or I made a mistake in the redirect method of the login screen, and an error occurred before the third implementation, and it took time to resolve ..

3. Display List in MySQL.

Let's start development. ** Make sure you can use "STS" and "MySQL" as prerequisites. ** **

3-1, Create a project.

"File"-> "New"-> "Spring Boot"-> "Spring Starter Project" Refer to the image below and match the app name, group name, JAVA version, etc.  SpringBoot_Di_Security_DB_2.png

SpringBoot_Di_Security_DB_3.png

・ Confirmation of the project. It is OK if the following files are created.  SpringBoot_Di_Security_DB_4.png

-Start the project. Start in the order of "Project right click"-> "Run"-> "Spring Boot application". If you do so, you will get the following error.

console



Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-01-16 15:39:27.382 ERROR 49572 --- [  restartedMain] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
	If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
	If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).

When I translated "Failed to configure a DataSource ...", the data source setting failed. It says that the driver cannot be identified. Furthermore, when translating "If you want an embedded database ...", it says that if you want to use the database, deploy it in the classpath, and if you want to load the database, activate the profile. From this, you can somehow understand that you need to write settings in the profile to use the MySQL you want to use this time.

If you want to use DB, edit "application.property".

3-2, Edit application.property.

<img width="220" alt="SpringBoot_Di_Security_DB_6.png SpringBoot_Di_Security_DB_5.png " src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/420031/bcbf58f1-9d17-16f3-472e-013050535ca7.png ">

application.property



#//  jdbc:mysql://localhost:3306/login_app is the URL of MySQl. After that, the time zone is set. For more information[Here](https://qiita.com/KKZ@github/items/e3f594b04c9233a86419)
spring.datasource.url=jdbc:mysql://localhost:3306/login_app?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
#//You are logged in as the root user.
spring.datasource.username=root
spring.datasource.password=
#//MySQL driver
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.jpa.database=MYSQL
spring.session.store-type=jdbc

3-3, Launch of MySQL.

Log in to MySQL at the command prompt or PowerShell. Below, the command that you actually type is the "execution command", and the one that simply shows the display result when the command is executed is the "command execution example". Please proceed while referring to it.

Execution command



mysql -uroot

create database login_app;

use login_app;

CREATE TABLE user(
    user_id INT AUTO_INCREMENT,
    user_name VARCHAR(255),
    password VARCHAR(255),
    PRIMARY KEY(user_id)
);

desc user;

INSERT INTO user (user_name, password) VALUES
(
    'user1',
    'pass1+'
),
(
    'yama',
    'kawa'
);

select * from user;

Command execution example



PS C:\Users\MGT-RD18> mysql -uroot
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2246
Server version: 5.7.28-log MySQL Community Server (GPL)

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>
mysql>
mysql> create database login_app;
Query OK, 1 row affected (0.02 sec)

mysql> use login_app;
Database changed
mysql>
mysql>

mysql>
mysql> CREATE TABLE user(
    ->     user_id INT AUTO_INCREMENT,
    ->     user_name VARCHAR(255),
    ->     password VARCHAR(255),
    ->     PRIMARY KEY(user_id)
    -> );
Query OK, 0 rows affected (0.05 sec)

mysql>
mysql> desc user;
+-----------+--------------+------+-----+---------+----------------+
| Field     | Type         | Null | Key | Default | Extra          |
+-----------+--------------+------+-----+---------+----------------+
| user_id   | int(11)      | NO   | PRI | NULL    | auto_increment |
| user_name | varchar(255) | YES  |     | NULL    |                |
| password  | varchar(255) | YES  |     | NULL    |                |
+-----------+--------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)

mysql>
mysql> INSERT INTO user (user_name, password) VALUES
    -> (
    ->     'user1',
    ->     'pass1+'
    -> ),
    -> (
    ->     'yama',
    ->     'kawa'
    -> );
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql>
mysql> select * from user;
+---------+-----------+----------+
| user_id | user_name | password |
+---------+-----------+----------+
|       1 | user1     | pass1+   |
|       2 | yama      | kawa     |
+---------+-----------+----------+
2 rows in set (0.00 sec)

mysql>


Finally, if the result of "select * from user;" is as above, it is OK. At this point the app should run.

3-4, Start the project.

Start in the order of "Project right click"-> "Run"-> "Spring Boot application". Then it should start normally. If there is no error, it is OK.

Console output example



 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.2.RELEASE)

2020-01-16 16:38:52.112  INFO 50328 --- [  restartedMain] SpringLogin.app.SpringLoginApplication   : Starting SpringLoginApplication on MGT-RD18 with PID 50328 (started by MGT-RD18 in C:\project\for test\SpringLogin)
2020-01-16 16:38:52.127  INFO 50328 --- [  restartedMain] SpringLogin.app.SpringLoginApplication   : No active profile set, falling back to default profiles: default
2020-01-16 16:38:52.174  INFO 50328 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2020-01-16 16:38:52.174  INFO 50328 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2020-01-16 16:38:52.822  INFO 50328 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JDBC repositories in DEFAULT mode.
2020-01-16 16:38:52.837  INFO 50328 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 12ms. Found 0 JDBC repository interfaces.
2020-01-16 16:38:53.137  INFO 50328 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-01-16 16:38:53.397  INFO 50328 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-01-16 16:38:53.413  INFO 50328 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-01-16 16:38:53.413  INFO 50328 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.29]
2020-01-16 16:38:53.491  INFO 50328 --- [  restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-01-16 16:38:53.491  INFO 50328 --- [  restartedMain] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1317 ms
2020-01-16 16:38:53.761  INFO 50328 --- [  restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-01-16 16:38:53.871  WARN 50328 --- [  restartedMain] ion$DefaultTemplateResolverConfiguration : Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
2020-01-16 16:38:54.058  INFO 50328 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2020-01-16 16:38:54.109  INFO 50328 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-01-16 16:38:54.113  INFO 50328 --- [  restartedMain] SpringLogin.app.SpringLoginApplication   : Started SpringLoginApplication in 2.453 seconds (JVM running for 3.661)
2020-01-16 16:39:08.470  INFO 50328 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-01-16 16:39:08.470  INFO 50328 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-01-16 16:39:08.485  INFO 50328 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 15 ms


In this state, the app itself started, but I can't check it on localhost: 8080. The reason is that I haven't created a View or controller to display.

3-5, Create View and controller and display the page for the time being.

The completed image is as follows. It will be confusing if you make everything suddenly, so we will make it step by step here as well. The goal this time is just to display the View for the time being.

SpringBoot_Di_Security_DB_6.png

-Create the necessary files and write the following code.

java:SpringLogin.app.controller/HomeController.java



package SpringLogin.app.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    //Execute Get method when accessing the address of userList.
    @GetMapping("/userList")
    public String getUserList(Model model) {
    	
    	//View can be called by specifying the file name under template.
        return "userList";
    }
}

templates/userList.html



<!DOCTYPE html>
<!--Describe the following to use thymeleaf. After that, th:If you set it to xxx, you can use the thymeleaf method.-->
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"></meta>
</head>
<body>
    <!--Content part-->
    <div>
    	<h1>User list</h1>
        <table>
            <tr>
                <th>user_id</th>
                <th>user_name</th>
                <th>password</th>
            </tr>
            <!--Receive userList from Controller th:Turn the elements of the array in each statement.-->
            <!--Since the instance of User class is stored in the argument user, the value can be retrieved by specifying the field name.-->
            <tr th:each="user : ${userList}">
                <td th:text="${user.userId}"></td>
                <td th:text="${user.userName}"></td>
                <td th:text="${user.password}"></td>
            </tr>

        </table>
    </div>
</body>
</html>

・ Once you have written it, you should be able to display it.

3-6, Display the application on the browser.

-Make sure that the Spring Boot application is running on Eclipse, and access the following. http://localhost:8080/userList

・ If the following screen is displayed, it is OK. Currently, the list itself cannot be displayed because the DB connection object is not implemented.

SpringBoot_Di_Security_DB_7.png

3-7, Implemented object for DB connection.

-The file configuration image is as follows. ** Red frame is newly created </ font>, Yellow frame is the file to be edited </ font>. ** **

SpringBoot_Di_Security_DB_8.png

-Also, I made an image of the processing flow, so please refer to it. (I'm sorry for the simple image every time ...)

SpringBoot_Di_Security_DB_9.png

-The code is as follows.

java:SpringLogin.app.controller/HomeController.java



package SpringLogin.app.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import SpringLogin.app.service.UserService;
import SpringLogin.app.model.User;

//Controller class annotation
@Controller
public class HomeController {

	//Create an instance and store it in a DI container.
    @Autowired
    UserService userService;

    //Execute Get method when accessing the address of userList.
    @GetMapping("/userList")
    public String getUserList(Model model) {

    	//@Call the userService method based on the instance created by Autowired.
        List<User> userList = userService.selectMany();
        //Pass the data received from userService to View side.
        model.addAttribute("userList", userList);

    	//View can be called by specifying the file name under template.
        return "userList";
    }

}

java:SpringLogin.app.service/UserService.java



package SpringLogin.app.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import SpringLogin.app.repository.UserDao;
import SpringLogin.app.model.User;

@Transactional //Annotation that rolls back when an exception occurs in a method, that is, returns just before the error occurs.
@Service //Service class annotation. The Service class has the role of converting the data received from DAO and passing it to Controller.
public class UserService {

    @Autowired
    @Qualifier("UserDaoJdbcImpl") //DAO seems to be the standard to impliments the interface, and the file to impliments is made explicit.
    UserDao dao;

    //Execute Dao method. Pass the return value to Controller as List type.
    public List<User> selectMany() {
        return dao.selectMany();

    }

}

java:SpringLogin.app.repository/UserDao.java



package SpringLogin.app.repository;

import java.util.List;

import org.springframework.dao.DataAccessException;

import SpringLogin.app.model.User;

public interface UserDao {

    //Get all data in User table.
    public List<User> selectMany() throws DataAccessException;

}


java:SpringLogin.app.repository/UserDaoJdbcImpl.java



package SpringLogin.app.repository;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import SpringLogin.app.model.User;
import SpringLogin.app.repository.UserDao;

@Repository("UserDaoJdbcImpl") //DB connection class annotation.
public class UserDaoJdbcImpl implements UserDao {

	//DI JdbcTemplate for DB connection.
    @Autowired
    JdbcTemplate jdbc;

    //Get all data in User table.
    @Override
    public List<User> selectMany() throws DataAccessException {

    	//Issue SQL with queryForList method. In the case of queryForList, the result will be returned in the form of List of Map type.
    	//Map is an array type object that is a set of Key and Value.
        List<Map<String, Object>> getList = jdbc.queryForList("SELECT * FROM user");
        
        //Create an instance of ArrayList for storage to convert to User type.
        List<User> userList = new ArrayList<>();

        for (Map<String, Object> map : getList) {

            User user = new User();
            // map.Get Value based on Key with get. Apply it to the Setter of the User class.
            //In this case DB user_The record linked from the column called id is acquired.
            user.setUserId((int) map.get("user_id"));
            user.setPassword((String) map.get("password"));
            user.setUserName((String) map.get("user_name"));

            // userList(List type array)Add User instance to.
            userList.add(user);
        }
        //Return List to Service.
        return userList;
    }


}


java:SpringLogin.app.model/User.java



package SpringLogin.app.model;

import lombok.Data;

@Data //Getter with one annotation because lombok is used,You can create a Setter.
public class User {

    private int userId;
    private String password;
    private String userName;

}


・ Once you have written the code, check it with your browser. If you haven't started the app in Eclipse, start it before checking. It is OK if the screen looks like the one below.

SpringBoot_Di_Security_DB_10.png

You can now use MySQL! ** Since it has become long, I will continue to Next time. ** **

Recommended Posts