123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- [[recipe-authenticating-with-a-database]]
- = Recipe: Authenticating with a Database
- NOTE: For this recipe, we secure the application described in the Basic Auth recipe. See <<security-cookbook-the-web-application>>.
- One of the most common ways to store user data is in a database.
- It is so common that Spring Security even has its own database schema with which it can work.
- You can either use the Spring Security schema or create a custom one.
- In this example, we use a MySQL database.
- You can use many other databases, though.
- Spring Security supports many databases, and you can write custom classes to support pretty much any database.
- For the sake of simplicity, we use the Spring Security schema in this example.
- To match the Spring Security schema, we can use the following SQL statements in MySQL's command line to create the tables we need:
- ====
- [source,sql]
- ----
- create table users(
- username varchar(50) not null primary key,
- password varchar(100) not null,
- enabled boolean not null
- );
- create table authorities (
- username varchar(50) not null,
- authority varchar(50) not null,
- constraint fk_authorities_users foreign key(username) references users(username)
- );
- ----
- ====
- We also need an index, which we can create with the following command:
- ====
- [source,sql]
- ----
- create unique index ix_auth_username on authorities (username,authority);
- ----
- ====
- From there, we can create our user record and set up its authority, as follows:
- ====
- [source,sql]
- ----
- insert into users(username,password,enabled) values('user','$2a$10$FShxdbQCgfQK/4D5r5siFe8Fx/MJesnji49Tttgk.4ax52mEwNS8y',true);
- insert into authorities(username,authority) values('user','ROLE_USER');
- ----
- ====
- What is going on with that password?
- We encoded the password (which is `password`, as it was in the basic auth example <<security-getting-started-basic-authentication,shown earlier>>) with bcrypt.
- The user types `password`, and `BCryptPasswordEncoder` turns it into that string for us so that it matches the value in the database.
- We cover that a bit later in this section.
- How did we get that string?
- We wrote a simple program that converts the string, `password`, into a bcrypt value.
- The following listing shows that program:
- ====
- [source,java]
- ----
- package security.utilities.passwordencoder;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- public class PasswordEncoder {
- public static void main(String[] args) {
- String encodedpassword=new BCryptPasswordEncoder().encode("password");
- System.out.println(encodedpassword);
- }
- }
- ----
- ====
- To get our database to work, we need to set some values in the `application properties` file (in the `resources` directory of our application).
- The following listing shows those values:
- ====
- [source]
- ----
- spring.datasource.driver-class-name=com.mysql.jdbc.Driver
- spring.datasource.url=jdbc:mysql://localhost:3306/security?useSSL=false
- spring.datasource.username=root
- spring.datasource.password=password
- ----
- ====
- CAUTION: Do NOT set your username to `root` and your password to `password` for a real application.
- We did it here because this is an example.
- To get the application to work, we have to add two dependencies: a MySQL connection and Spring Data JDBC.
- The following listing shows the new `pom.xml` file:
- ====
- [source,xml]
- ----
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.2.0.RELEASE</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
- <groupId>com.example</groupId>
- <artifactId>securing-web</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <name>securing-web</name>
- <description>Demo project for Spring Boot</description>
- <properties>
- <java.version>1.8</java.version>
- </properties>
- <dependencies>
- <dependency> <1>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>5.1.25</version>
- </dependency>
- <dependency> <2>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-jdbc</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-thymeleaf</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <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>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- <exclusions>
- <exclusion>
- <groupId>org.junit.vintage</groupId>
- <artifactId>junit-vintage-engine</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- </dependencies>
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
- </project>
- ----
- <1> The connector dependency.
- <2> The Spring Data JDBC dependency.
- ====
- We also need substantial changes to our `WebSecurityConfig` class.
- In particular, we can remove the `UserDetailsService` bean, and we need to add a `configure` method that uses `AuthenticationManagerBuilder` as a parameter.
- We also need to define a data source (which finds our database).
- Note that this method is an override of the `configure` method in `WebSecurityConfigurerAdapter`.
- The following listing shows our new `WebSecurityConfig` class:
- ====
- [source,java]
- ----
- package com.example.securingweb;
- import javax.sql.DataSource;
- import org.springframework.beans.factory.annotation.Autowired;
- 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.EnableWebSecurity;
- import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- @Configuration
- @EnableWebSecurity
- public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
- @Autowired
- private DataSource dataSource; <1>
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- .authorizeRequests()
- .antMatchers("/", "/home").permitAll()
- .anyRequest().authenticated()
- .and()
- .formLogin()
- .loginPage("/login")
- .permitAll()
- .and()
- .logout()
- .permitAll();
- }
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception { <2>
- auth.jdbcAuthentication().dataSource(dataSource)
- .usersByUsernameQuery("select username, password, enabled"
- + " from users where username=?")
- .authoritiesByUsernameQuery("select username, authority "
- + "from authorities where username=?")
- .passwordEncoder(new BCryptPasswordEncoder());
- }
- }
- ----
- <1> Autowire the data source.
- <2> The `configure` method that has `AuthenticationManagerBuilder` as a parameter.
- ====
- We rely on Spring Boot to find our database (from the information in `application.properties`), so we need only autowire it here to get it to work.
- What does that `configure(AuthenticationManagerBuilder auth)` method do?
- The `AuthenticationManagerBuilder` exposes a method called `jdbcAuthentication`, which supports chaining other methods to define the user query that we use to see if a user matches the user name and password provided in the HTML form.
- The `jdbcAuthentication` method lets us specify the data source and then add two queries, one for the user and one for the authority.
- It also lets us specify the password encoder.
- Since we specify `BCryptPasswordEncoder`, the password provided by the user in the form matches the bcrypt-encoded password that we inserted into the database earlier, so long as the user types `password`.
- Why do we not need `UserDetailsService`?
- The `jdbcAuthentication` method provides a `JdbcUserDetailsManagerConfigurer` object, which does the same thing as `UserDetailsService` and lets us connect to a database.
- `AuthenticationManagerBuilder.jdbcAuthentication` is the heart of this example.
|