Try   HackMD

01.5-SpringBoot basics-Lesson5 Data Persistence & Security

tags: Udacity

1.Data Persistence & Security

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

The Lesson Outline

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Lesson Outline

  • ORM is the Norm: We introduce ORM, or object-relational-mapping, a software pattern that leverages the similarities between Java classes and SQL tables to eliminate boilerplate in data access code.

  • MyBatis Mappers: We introduce MyBatis, a dead-simple ORM tool for Java that integrates well with Spring. We discuss the "Mapper" classes MyBatis wants us to design to access the database.

  • Practical Example - User Credentials and Authentication: As a motivating example for using ORM, we discuss how to implement basic login security with a User table, MyBatis, and Spring Security. We walk through a lengthy sample project that implements the entirety of this motivating example.

Shannon Note

ORM : Object Relational Mapping, 這是一個方式去思考SQL table rows as Java Classes和個別的物件,你會學到如何去設計Java Classes去match data.簡單來說它是一個幫助使用者更簡單 安全的去從資料庫讀取資料,它的特性為透過程式語言去操作資料庫語言

ORM優點:

  1. 安全性: 可以避免SQL injection
  2. 簡化性: 他可以簡化SQL語法
  3. 通用性: 未來如果有資料庫轉移的問題,比較不會遇到要改寫程式的狀況

ORM缺點:

  1. 效能: 因為多了把程式語言轉譯成SQL語言
  2. 學習曲線高: 對初學者來說需要學SQL語法跟程式語言
  3. 複雜查詢維護性低: 就可能會用到原生地SQL語法

Concurrence: two or more events or circumstances happening or existing at the same time.

資料參考: 資料庫設計概念 - ORM https://ithelp.ithome.com.tw/articles/10207752

2.Big Picture

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

The Growing Layers of Our Application
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Adding a database to our application is a way to externalize data persistence problems. When storing data in memory at runtime, we struggle to deal with:

  • Storage Space
  • Concurrency (衝突?)
  • Persistence of Data

Using a database allows us to isolate these concerns from the rest of our application, so we can focus on the business logic of our application.

There are many ways to manage the communication between an application and a database. For this course, we'll be using the library MyBatis to transform Java objects to SQL query parameters and to transform SQL query results into Java objects.

We'll create MyBatis Mappers as Spring beans, so we can inject them into any other beans that need them. For example, if we think about an online-shopping example, we might have a UserMapper that manages credentials and profile information and a CartMapper that manages the contents of an individual user's cart. We can inject the UserMapper into a Checkout Service that also receives the CartMapper to apply the charges in a User's cart to that User's stored payment information.

Later in this lesson, we'll combine our MyBatis mappers with Spring Security to authenticate each user's session automatically. To continue our earlier example, this means we could inject the UserMapper in some kind of Authentication Service to check client credentials on login.

Shannon Note

JDBC : Java DataBase Connectivity

  • 跟IoC搭配起來不好用

MyBatis: 可以自動地把object轉換到SQL Query Parameters and SQL query Results back into objects.像是insert into users values到Database,又或是透過select * from來取得object裡面的內容

  • Onion Archetecture
    * 第一層External Request
    * 第二層Controller
    * 第三層Services
    * 第四層Mappers: 主要用object class去代表table的data,然後透過object mapper class去寫query。
  • User Login with Spring Security
    * 我們可以透過Spring Security去連接Mapper, 我們就可以告訴透過存在Database裡面的存證去檢查login。
    * Spring Security可以自動追蹤users也可以給予user權限

3.Intuition

Developing Your Intuition About ORM and Security

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

What Data Should be Stored in a Database?

  • Data shared across multiple user sessions, like a product inventory
  • Persistent data that should remain accessible after being logged out, like user profile or shopping cart

How Should Data be Structured?

  • Intuitively. Most data can be stored in a similar format to the data objects that represent it in Java, with attributes matching column names.
  • Differing. Some data must be stored differently for security reasons, such as encrypted passwords. Other data may require a different format for efficient storage, such as large files.

Thinking about Security
The main question to ask is: “What pages can a user access?”

  • User-Specific Data
  • Generally Accessible (Unsecured) Data
  • May Vary by Domain

4.ORM is the Norm

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

The first step in using ORM is to define the data model. Consider the relationship between:

  • A User
  • Their Shopping Cart
  • The Store Inventory

We can represent their relationship in SQL with this image below.

Relationship Between SQL Tables

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

A primary feature of ORM is that this type of relationship should have a natural mapping to Java classes. We can represent this same data in Java using a simple class diagram.

Class Diagram Corresponding to SQL Tables

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

The data types of these class attributes correspond to the data types of the SQL columns. Some Java types can be mapped to many different SQL types, and some SQL types can be mapped to multiple Java types, but in this case the type mappings are obvious. For a full list of the MyBatis type mapping, consult the MyBatis 3 TypeHandlers list.

Once you have defined your data types, MyBatis can automatically map Java objects to SQL statements.

ORM Process Visualization

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Key Terms

  • ORM: Object-Relational Mapping. A general term describing a set of technology that can be used to automatically convert data between database representation and application representation.
  • Mapping: Drawing a relationship between a field in a Java class and a column in a SQL table.
  • One to One: A relationship between two objects in which one entity is on each side of the relationship.
  • Many to Many: A relationship between two objects in which multiple copies of each entity can be related to multiple copies of the other entity.

5.Exercise: ORM is the Norm

Welcome to big business! You’ve been hired to implement the data model for a Taco Delivery service. Yum!

The diagram below depicts a relationship between customers, tacos, and deliveries. Each Customer can have as many Orders as they want. Each Order must have one or more entries for TacoOrder. The TacoOrder entries have a name, price, and count. Lastly, you can optionally schedule each Order for a Delivery.

Your job is to make a class for each of these tables. There should be a Customer, Order, TacoOrder, and Delivery class. The variables in the classes should match the variables in the tables.

Taco Delivery Database Diagram

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

6.Solution: ORM is the Norm

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

You should have created four classes for this exercise: Customer, Order, TacoOrder, and Delivery. These class files should live in a new data folder in the course1 folder. The variables in each class should correspond to the variables in the database tables. Here are some sample implementations:

Customer.java

public class Customer {
   private Integer id;
   private String userName;
   private String password;

   /* getters and setters not shown */
}

Order.java

public class Order {
   private Integer id;
   private Integer customerId;

   /* getters and setters not shown */
}

TacoOrder.java


public class TacoOrder {
   private Integer orderId;
   private String tacoName;
   // this will work here, but you should often use BigDouble for prices 
   // if you plan to do any math with them
   private Double tacoPrice;
   private Integer count;

  /* getters and setters not shown */
}

Delivery.java

public class Delivery {
   private Integer id;
   private Integer orderId;
  // there are a few types you can use for this. 
  // java.sql.Timestamp contains both date and time
   private Timestamp time;

   /* getters and setters not shown */
}

7.MyBatis Mappers

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

sample code click here

MyBatis provides a shallow ORM layer over JDBC (Java Database Connectivity). That means it helps map your Java objects to queries that save and retrieve data using JDBC.

What is ORM(Object Relational Mapping?

MyBatis is mostly used through interface definitions. MyBatis automatically generates classes that implement the interface and makes them available as Spring beans. This is an example interface that defines a MyBatis Mapper.

@Mapper
public interface UserMapper {
   @Select("SELECT * FROM USERS WHERE username = #{username}")
   User getUser(String username);
}

補充: https://codertw.com/程式語言/410426/

This code uses #{username} to identify the username parameter. It's like Thymeleaf parameters, but for SQL!

For more information on the template syntax MyBatis uses for SQL, check out mybatis official documentation.

mybatis documentation click here

There are additional annotations for @Insert, @Update, and @Delete as well.

See the further research section below the next video for more info on ways to configure MyBatis.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

sample code click here

The @Insert annotation automatically references attributes on the user object. Note username, firstName, lastName are all referenced directly here, because they are attributes of the user object.

@Mapper
public interface UserMapper {
   @Insert("INSERT INTO USERS (username, salt, password, firstname, lastname) " +
           "VALUES(#{username}, #{salt}, #{password}, #{firstName}, #{lastName})")
           //設定Primary key是誰
   @Options(useGeneratedKeys = true, keyProperty = "userId")
   //他會回傳userid, 因此回傳值設定int
   int insert(User user);
}

This example also demonstrates the @Options annotation. @Insert normally returns an integer that is the count of rows affected. By using the @Options annotation, we're telling MyBatis that we want to automatically generate a new key and put it in userId. Now the method will return the new userId once the row has been inserted.

All we have to do to use these methods is inject beans for this interface into our services and MyBatis will automatically create the code for the JDBC requests!

MyBatis Mappers Lie at the Center of Out Onion Architecture

Key Terms

  • @Select, @Insert, @Update, @Delete: Annotations representing SQL statements to be executed. Each annotation takes a string for a SQL statement of the corresponding type. For example, a @Select annotation takes a string for a SQL SELECT statement.
  • @Options: Annotation providing access to switches and configuration options for JDBC statements.

Further Research

  • For a full list of the available MyBatis annotations and some example usage, see the MyBatis Java API documentation.
  • For an informal overview of result mapping with MyBatis annotations, see this Medium article

8.Exercise: MyBatis Mappers

Exercise: MyBatis Mappers
Earlier in this lesson we created some classes to help support our burgeoning Taco delivery business. Let’s create a @Mapper interface that we can use to create new deliveries once our customer has successfully placed an order. As a reminder, here’s our Delivery class:

public class Delivery {
   private Integer id;
   private Integer orderId;
   private Timestamp time;
   /* getters and setters not shown */
}

You’ll be presented with the DeliveryMapper interface, and it’s your job to declare methods to SELECT, INSERT, and DELETE Delivery records. Annotate each method with the appropriate annotations to perform the Select, Insert, and Delete operations as described here:

  • The findDelivery (or select) method should take the id of our Delivery class and return the corresponding Delivery object.
  • The insert method should take a Delivery object and create a new record with the orderId and time. It should generate a new id and return that id.
  • The delete method should take an id and delete that record from the database. It doesn’t need to return anything.

9.Solution:MyBatis Mappers

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

We created three new methods in our interface. In this solution they’re named findDelivery, insert, and delete, but they can be named anything.

@Mapper
public interface DeliveryMapper {
   @Select("SELECT * FROM Delivery WHERE id = #{id}")
   Delivery findDelivery(Integer id);

   @Insert("INSERT INTO Delivery (orderId, time) VALUES(#{orderId}, #{time})")
   @Options(useGeneratedKeys = true, keyProperty = "id")
   Integer insert(Delivery delivery);

   @Delete("DELETE FROM Delivery WHERE id = #{id}")
   void delete(Integer id);
}

The first annotation is a simple @Select like we saw earlier in the lesson, and the second annotation is pretty similar to the earlier example as well.

It uses INSERT to create a new row in the Delivery table. Note that it only needs to provide the orderId and time values, because the id itself is generated. The @Options annotation indicates to generate the key for the id property and return it from the method. Also note that our VALUES portion of the query just provides orderId and time directly. MyBatis can figure out that they are attributes of our Delivery object.

Lastly, @Delete is very similar to the @Select, but make sure you use the right annotation for it!

Shannon Note

如果id是自己產生的,有設定@Option,在insert的時候就不需寫ID Value,寫了@option就需要寫一個方法來收到新增的delivery id。

如果使用@Delete他的回傳值是void,透過輸入id來執行@delete

//用來設定要不要自動產生主key編號
  boolean useGeneratedKeys() default false;
//用來表示object屬性為key
  String keyProperty() default "";
//用來表示database cloumn為key
  String keyColumn() default "";

https://youtu.be/MWAX6ppeBoc

10.User Credentials

Practical Example: User Credentials and Authentication

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

User support is a common feature in web applications, which means that a user can register an account and use credentials to login to the application in the future.

It's important to design databases with the assumption that they will someday be breached, and so we cannot store passwords or other secret credentials in plain text. Two approaches to storing passwords are:

  • Encryption: Modifying data before storing it, with the intention of using another algorithm to return the data to its original form once it needs to be used.
  • Hashing: Modifying data before storing with the intention of never returning it to its original form. The modified data will be compared to other modified data only.

Hashing and Encryption should occur in a service dedicated to that purpose, rather than on the front end or in the controller. Remember separation of concerns and our onion architecture! The idea is that all user flows originate externally, travel through a controller, then through one or more services, finally through a data access bean to the database, and then all the way back up the chain. Structuring applications this way makes it easy to follow dependencies and separate concerns, so that's how we're going to build applications from now on.

For a more in-depth discussion of salting and hashing passwords, see the further research section below the videos for this section.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

For the full lecture sample code from this video and the next, check here

Below is an example of how to hash user passwords in the database. First, we have the User class and the UserMapper. When our UserService creates a new user, it uses a hashing service to convert the password to a hashed value before saving it.

User.java

public class User {
    private Integer userId;
    private String username;
    private String salt;
    private String password;
    private String firstName;
    private String lastName;
    /* constructor, getters, and setters omitted */
}

UserMapper.java

@Mapper
public interface UserMapper {
    @Select("SELECT * FROM USERS WHERE username = #{username}")
    User getUser(String username);

    @Insert("INSERT INTO USERS (username, salt, password, firstname, lastname) VALUES(#{username}, #{salt}, #{password}, #{firstName}, #{lastName})")
    @Options(useGeneratedKeys = true, keyProperty = "userId")
    int insert(User user);
}

Method in UserService.java

public int createUser(User user) {
    SecureRandom random = new SecureRandom();
    byte[] salt = new byte[16];
    
    //產生一個random value
    random.nextBytes(salt);
    //用Base64 encoder對Salt去加密成一個形式以至於讓我們的hashservice能夠understand
    String encodedSalt = Base64.getEncoder().encodeToString(salt);
    //然後將加密好的Salt還有密碼透過hashService進行hash
    String hashedPassword = hashService.getHashedValue(user.getPassword(), encodedSalt);
    //透過userMapper去insert新的帳號
    return userMapper.insert(new User(null, user.getUsername(), encodedSalt, hashedPassword, user.getFirstName(), user.getLastName()));
}

The hashing service itself has a single method that takes some data and salt and creates a string representing the hashed value.

  • Salt: random data that is combined with the input string when hashing so that the resultant hashed values are unique for each row. This means that two users with the same password would not have the same hash in the database.

Method in HashService.java

public String getHashedValue(String data, String salt) {
    byte[] hashedValue = null;

    KeySpec spec = new PBEKeySpec(data.toCharArray(), salt.getBytes(), 5000, 128);
    try {
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        hashedValue = factory.generateSecret(spec).getEncoded();
    } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
        logger.error(e.getMessage());
    }

    return Base64.getEncoder().encodeToString(hashedValue);
}

When a user logs in, we have no way to retrieve their original password, but we can re-hash their user input and see if it matches the hashed value in our database. Below is an example AuthenticationService that implements the AuthenticationProvider class. This allows Spring to integrate our provider with many different authentication schemes, but we can see in our supports method that we specify that we only support UsernamePasswordAuthentication.

The authentication method takes an Authentication object from spring and returns an authentication token if the user's credentials are correct.

AuthenticationService.java

@Service
public class AuthenticationService implements AuthenticationProvider {
    private UserMapper userMapper;
    private HashService hashService;

    public AuthenticationService(UserMapper userMapper, HashService hashService) {
        this.userMapper = userMapper;
        this.hashService = hashService;
    }

    //這個authenticate method是專門給spring security去檢查憑證
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //收到user提交的帳號及密碼
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
        
        //透過usermapper去檢查是否真的有這個username
        User user = userMapper.getUser(username);
        if (user != null) {
            //取得在資料庫這位user的salt
            String encodedSalt = user.getSalt();
            //拿到salt後跟這位陌生人提交的密碼進行hash
            String hashedPassword = hashService.getHashedValue(password, encodedSalt);
            //如果密碼依樣
            if (user.getPassword().equals(hashedPassword)) {
            //這個usernamePasswordAuthenticationToken.class代表著特定username和password的成功授權
            //ArrayList 先保留空 超過課程範圍
                return new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>());
            }
        }

        return null;
    }

    //這個support告訴Spring可以處理那些authentication
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

For the full lecture sample code from the past two videos, check here

In order for Spring to actually use our AuthenticationService, we need to extend our Web Security configuration. We do that with an adapter for the WebSecurityConfigurer. This example overrides two configure methods:

  • configure(AuthenticationManagerBuilder auth): used to tell Spring to use our AuthenticationService to check user logins
  • configure(HttpSecurity http): used to configure the HttpSecurity object by chaining methods to express security requirements

SecurityConfig.java

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private AuthenticationService authenticationService;
    
    public SecurityConfig(AuthenticationService authenticationService) {
        this.authenticationService = authenticationService;
    }
    
    //主要是告訴Spring去使用我們的AuthenticationService去確認user logins
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(this.authenticationService);
    }

    //被用來去設定HttpSecurity object by chaining methods去表示Security的需求
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //不用登入的人可以看得URL
        http.authorizeRequests()
                //這個代表signup, css, js的網址一般人可以取得
                .antMatchers("/signup", "/css/**", "/js/**")
                //permitAll: tell spring permit all requests to those URLs without authorization
                .permitAll() 
                //anyRequest: to match any request that wasn't matched by this first call
                .anyRequest()
                //authenticated: tells spring to authenticate any requests that haven't been matched otherwise
                .authenticated();
        
        //login是給別人登入的地方所有人都可以取得
        http.formLogin()
                .loginPage("/login")
                .permitAll();
                
        //如果想要跳去signup, css, js, login以外的地方都會跳回去/home
        http.formLogin()
                .defaultSuccessUrl("/home", true);
    }
}

We can see that the second configure method does four things:

  • Allows all users to access the /signup page, as well as the css and js files.
  • Allows authenticated users to make any request that's not explicitly covered elsewhere.
  • Generates a login form at /login and allows anyone to access it.
  • Redirects successful logins to the /home page.

To see the HashService class used in this example, click here.

Key Terms

  • Onion Pattern: Sometimes also called Tiered Architecture, Multi-tiered Architecture, or n-tiered Architecture. This is a design pattern that separates areas of the application into controller, service, and data layers (and sometimes more). User flows originate from the controller tier, which passes them to the service tier, which then reaches a data access bean.
  • Encryption: Modifying data before storing it, with the intention of using another algorithm to return the data to its original form once it needs to be used.
  • Hashing: Modifying data before storing with the intention of never returning it to its original form. The modified data will be compared to other modified data only.
  • Salt: random data that is combined with the input string when hashing so that the resultant hashed values are unique for each row. This means that two users with the same password would not have the same hash in the database.

Further Research

Shannon Note

  • plan text 明文 encrypted text 密文
    Two apporaches to storing passwords are:
  • Encryption: 加密後可以解密回來
  • Hashing: 加密後不可以解密回來

UserService.java介紹
UserService主要目的是在檢查這位使用者是否為本人,然後因為implements >AuthenticationProvider,所以需要實作兩個方法*

  • authenticate
    * Spring Security確認收到的憑證,憑證通常來自form,然後要告訴authenticate會接收到一個Authentication的object然後回傳相同的類,Spring期望我們去讀取authentication object裡面的來自input的credential去確認她是valid。如果確定是本人就可以login,拿到權限
  • Support
    * 告訴Spring可以處理那些authentication

SecurityConfig.java

  • @Configuration : Spring will use this as a source of configuration for the IoC context.
  • @EnableWebSecurity : 讓Spring know that this class secifically is configuring Spring Security.
  • 他繼承Abstract class WebSecurityConfigurerAdapter
    * 主要是讓我們去寫一個Adaptere給Spring WebSecurityConfigurer
    * 實作兩個方法 different types of configure method
    * 第一個configure method
    * 參數: AuthenticationManagerBuilder
    * 內容: 透過AuthenticationManagerBuilder(Builder)上的方法來添加一個configure parts of the authentication scheme for our app
    * 第二個configure method
    * 他定義Spring如何接收authorization要求

SecureRandom:https://www.cnblogs.com/deng-cc/p/8064481.html

11.Exercise: User Credentials

Exercise: User Credentials and Authentication
Our Taco Delivery service has been getting bombarded with fake orders, so it’s time to make sure only authenticated users can submit orders. Here’s our SecurityConfig class with a configure method already defined.

Your job is to fill in the method so it fulfills the following goals:

  1. Permit all requests to the /order page, as well as to all files in our /css and /js directories.
  2. Allow authenticated requests to any pages not specifically identified.
  3. Allow unauthenticated users to access the automatically-generated login page at /login; and
  4. Redirect users to the /tacos page once they’re logged in, so they can immediately pick out which delicious tacos to buy.

12.Solution: User Credentials

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Here is an example configure method that meets our requirements:

@Override
protected void configure(HttpSecurity http) throws Exception {
   http.authorizeRequests()
           .antMatchers("/order", "/css/**", "/js/**").permitAll()
           .anyRequest().authenticated();
   http.formLogin()
           .loginPage("/login")
           .permitAll();
   http.formLogin()
           .defaultSuccessUrl("/tacos", true);
}

The first method chain permits all requests to the /order page or to our css and js directories, and then it allows authenticated users to make any kind of request. The next chain permits all users to access the auto-generated login page at /login. Remember that Spring creates this for us. Lastly, the third method chain redirects successful logins to the /tacos page by default.

13.Exercise: Final Review

Exercise: Final Review
For this final review, you'll be updating your chat app from the last lesson to support user registration and login, as well as to store chat messages in a database rather than the in-memory list we created last lesson. That means you're going to have do a couple of things:

Re-enable Spring Security. Last lesson, we commented out the Spring Security dependencies in the project pom.xml file. This time we need to add it back! As a reminder, these are the two dependencies you need to un-comment (or add back to your project if you deleted them last time):

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

Define the database tables for the app. But wait, you say, didn't you tell us that we didn't have to know how to design SQL tables? That's right, I did! You won't have to design the tables yourself, but you do have to add the definitions I give you to your project. Specifically, you have to create a schema.sql file in the src/main/resources directory of your project. Here's the code you need to put in that file:

CREATE TABLE IF NOT EXISTS USERS (
 userid INT PRIMARY KEY auto_increment,
 username VARCHAR(20),
 salt VARCHAR,
 password VARCHAR,
 firstname VARCHAR(20),
 lastname VARCHAR(20)
);

CREATE TABLE IF NOT EXISTS MESSAGES (
 messageid INT PRIMARY KEY auto_increment,
 username VARCHAR NOT NULL,
 messagetext VARCHAR NOT NULL
);

We haven't talked about this file much, but it's been in all of the examples we've looked at this lesson. Spring Boot will, by default, look for this file when the server starts and execute it if it's present. You can use this behavior for a variety of useful database-related tasks, but all we're using it for here is to create two tables in the database - USERS and MESSAGES.

Create Java classes that match the database tables. If we want to take advantage of MyBatis, we need to create a User class that matches the data in the USERS table above, and we need to update the ChatMessage class from last lesson's final review to match the MESSAGES table above. For the latter, you only need to add a single field, but I'll let you figure out which one.

Create MyBatis mapper classes to handle interacting with the database. As we discussed this lesson, we need to create specialized @Mapper-annotated interfaces to access our database tables. You'll need two for this project - one for the USERS table and one for the MESSAGES table.

Add user registration and login support to the application, and require a login to access the /chat URL. For this, I recommend looking at the example code from the practical example earlier in the lesson. Here's the link to the full project for reference. You'll have to implement the following elements from that example in this project as well:

  • The AuthenticationService, HashService, and UserService classes
  • The LoginController and SignupController classes
  • The SecurityConfig class
  • The login.html and signup.html HTML templates
  • And (optionally) the /css and /js folders found in src/main/resources/static.

Most of these can be copied almost verbatim, but it's good practice for you to check and make sure you understand what needs to be copied, what needs to be changed, and why.

Update the MessageService to use the database instead of an in-memory list of messages. That means it's going to have to use the MessageMapper you created!

Update the chat.html template to remove the username input from the message submission form. If we want the user to log in before seeing the chat page, we shouldn't make them enter their own username. Plus, we don't want to have users impersonating each other!

Update the ChatController to get the currently-logged-in user's username upon message submission. You'll have to do some research for this one! It's common to need the currently-logged-in user's credentials when processing requests, so the solution shouldn't be too hard to find. You also need to attach the username to the message before passing it off to the UserService - but again, that's up to you to figure out.

[BONUS] add a logout button to the chat template. It may not seem incredibly important right now, but generally it's actually very necessary to allow users to log out from an application. Many people across the world browse the internet from public computers in libraries and internet cafes, and even more share the same computer with friends and family. Our security implementation won't be worth much if the next person who uses the app continues to use the previous person's account! For this bonus task, you'll again have to do some research, this time into how Spring Security handles logout actions. I believe in you!

And that's it for this final review! I know these instructions might seem intimidating - but once you're actually implementing them, I think you'll find that it's less work than it seems. Good luck!

Final Review Exercise
It's time to add data persistence and security to our chat application! Update your previous final review project according to the tasks below, and refer back to the instructions above for the high-level goals and code snippets to include in your project.

  1. Re-enable Spring Security
  2. Create a schema.sql file in src/main/resources and add the USERS and MESSAGES table definitions from the instructions above.
  3. Create java classes to model the SQL tables you defined.
  4. Add user registration and login support using Spring Security
  5. Restrict access to the /chat URL to logged-in users.
  6. Update the MessageService class to use the mappers you created to add and retrieve messages to and from the database instead of an in-memory list.
  7. Update the chat/html template to remove the username input field from the message submission from.
  8. Update the ChatController to retrieve the currently-logged-in user's username from Spring Security when handling a message submission
  9. Manually test your app! Are you restricted from accessing the chat page withou logging in? Can you register a new user? Can you log in with that user's credentials? When that user sends a message, does their registered username show up next to the message? Can you log in as another user and verify that it still shows up with the first user's username?

14. Final Review

Exercise: Final Review
For this final review, you'll be updating your chat app from the last lesson to support user registration and login, as well as to store chat messages in a database rather than the in-memory list we created last lesson. That means you're going to have do a couple of things:

Re-enable Spring Security. Last lesson, we commented out the Spring Security dependencies in the project pom.xml file. This time we need to add it back! As a reminder, these are the two dependencies you need to un-comment (or add back to your project if you deleted them last time):

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

Define the database tables for the app. But wait, you say, didn't you tell us that we didn't have to know how to design SQL tables? That's right, I did! You won't have to design the tables yourself, but you do have to add the definitions I give you to your project. Specifically, you have to create a schema.sql file in the src/main/resources directory of your project. Here's the code you need to put in that file:

CREATE TABLE IF NOT EXISTS USERS (
 userid INT PRIMARY KEY auto_increment,
 username VARCHAR(20),
 salt VARCHAR,
 password VARCHAR,
 firstname VARCHAR(20),
 lastname VARCHAR(20)
);

CREATE TABLE IF NOT EXISTS MESSAGES (
 messageid INT PRIMARY KEY auto_increment,
 username VARCHAR NOT NULL,
 messagetext VARCHAR NOT NULL
);

We haven't talked about this file much, but it's been in all of the examples we've looked at this lesson. Spring Boot will, by default, look for this file when the server starts and execute it if it's present. You can use this behavior for a variety of useful database-related tasks, but all we're using it for here is to create two tables in the database - USERS and MESSAGES.

Create Java classes that match the database tables. If we want to take advantage of MyBatis, we need to create a User class that matches the data in the USERS table above, and we need to update the ChatMessage class from last lesson's final review to match the MESSAGES table above. For the latter, you only need to add a single field, but I'll let you figure out which one.

Create MyBatis mapper classes to handle interacting with the database. As we discussed this lesson, we need to create specialized @Mapper-annotated interfaces to access our database tables. You'll need two for this project - one for the USERS table and one for the MESSAGES table.

Add user registration and login support to the application, and require a login to access the /chat URL. For this, I recommend looking at the example code from the practical example earlier in the lesson. Here's the link to the full project for reference. You'll have to implement the following elements from that example in this project as well:

  • The AuthenticationService, HashService, and UserService classes
  • The LoginController and SignupController classes
  • The SecurityConfig class
  • The login.html and signup.html HTML templates
  • And (optionally) the /css and /js folders found in src/main/resources/static.

Most of these can be copied almost verbatim, but it's good practice for you to check and make sure you understand what needs to be copied, what needs to be changed, and why.

Update the MessageService to use the database instead of an in-memory list of messages. That means it's going to have to use the MessageMapper you created!

Update the chat.html template to remove the username input from the message submission form. If we want the user to log in before seeing the chat page, we shouldn't make them enter their own username. Plus, we don't want to have users impersonating each other!

Update the ChatController to get the currently-logged-in user's username upon message submission. You'll have to do some research for this one! It's common to need the currently-logged-in user's credentials when processing requests, so the solution shouldn't be too hard to find. You also need to attach the username to the message before passing it off to the UserService - but again, that's up to you to figure out.

[BONUS] add a logout button to the chat template. It may not seem incredibly important right now, but generally it's actually very necessary to allow users to log out from an application. Many people across the world browse the internet from public computers in libraries and internet cafes, and even more share the same computer with friends and family. Our security implementation won't be worth much if the next person who uses the app continues to use the previous person's account! For this bonus task, you'll again have to do some research, this time into how Spring Security handles logout actions. I believe in you!

Shannon Note

小提示
* https://stackoverflow.com/questions/22557741/logout-link-with-spring-and-thymeleaf
* https://docs.spring.io/spring-security/site/docs/3.2.0.RC2/apidocs/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.html#logoutUrl(java.lang.String)
* https://www.baeldung.com/spring-security-logout

And that's it for this final review! I know these instructions might seem intimidating - but once you're actually implementing them, I think you'll find that it's less work than it seems. Good luck!

14.Solution: Final Review

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

For the full solution code, check here.

Glossary

  • ORM: Object-Relational Mapping. A general term describing a set of technology that can be used to automatically convert data between database representation and application representation.
  • JDBC: Java Database Connectivity API, which is a specification for making SQL requests from Java.
  • MyBatis: A thin ORM over JDBC that automatically generates code to execute SQL statements over JDBC and maps the results to Java objects.
  • Mapping: Drawing a relationship between a field in a Java class and a column in a SQL table.
  • One to One: A relationship between two objects in which one entity is on each side of the relationship.
  • Many to Many: A relationship between two objects in which multiple copies of each entity can be related to multiple copies of the other entity.
  • @Select, @Insert, @Update, @Delete: Annotations representing SQL statements to be executed. Each annotation takes a string for a SQL statement of the corresponding type. For example, a @Select annotation takes a string for a SQL SELECT statement.
  • @Options: Annotation providing access to switches and configuration options for JDBC statements.
  • Onion Pattern: Sometimes also called Tiered Architecture, Multi-tiered Architecture, or n-tiered Architecture. This is a design pattern that separates areas of the application into controller, service, and data layers (and sometimes more). User flows originate from the controller tier, which passes them to the service tier, which then reaches a data access bean.
  • Encryption: Modifying data before storing it, with the intention of using another algorithm to return the data to its original form once it needs to be used.
  • Hashing: Modifying data before storing with the intention of never returning it to its original form. The modified data will be compared to other modified data only.
  • Salt: random data that is combined with the input string when hashing so that the resultant hashed values are unique for each row. This means that two users with the same password would not have the same hash in the database.

15.Lesson Conclusion

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

This lesson covered a lot! First we covered persistent data and learned:

  • How to Identify Persistent Data
  • Where to Store Persistent Data (SQL, in This Case)
  • How to Use MyBatis, a Simple ORM Framework to Access Our Database
  • How to Integrate MyBatis with Spring Through Simple Annotations

We also took a peek at Security!

  • Explored a Real-World Example of Database Access
  • Configured Simple Username/Password Authentication
  • Connected Spring Security’s Configuration with Our User Credentials

We’ve also taken our chat program to the next level by adding persistent storage and authentication to it!