Create new Maven Project
Create new project with Spring Initializr (https://start.spring.io/) and add dependency: Spring Data JDBC and MySQL
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
Add Spring Data JDBC properties src/main/resources/application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=user
spring.datasource.password=changeit
Start a local mysql server with a container image.
https://catalog.redhat.com/software/containers/rhel9/mysql-80/61a60915c17162a20c1c6a34
https://hub.docker.com/_/mysql
$ podman run -d --name mysqld \
-e MYSQL_USER=user \
-e MYSQL_PASSWORD=changeit \
-e MYSQL_DATABASE=mydb \
-e MYSQL_ROOT_PASSWORD=changeit \
-p 3306:3306 \
docker.io/library/mysql:8.0
$ podman logs --follow mysqld
Create and populate DB. Use below test method to generated hashed password.
@Test
public void test() throws Exception {
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
System.out.println(passwordEncoder.encode("changeit"));
}
Now connect to mysql container interactive and execute sql commands to creae tables and populate them with data.
https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/jdbc.html
$ podman exec -it mysqld /bin/bash
bash-4.4# mysql -u root -p mydb
create table users(username varchar(50) not null primary key, password varchar(500) 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));
create unique index ix_auth_username on authorities (username, authority);
INSERT INTO users (username, password, enabled) VALUES ('john', '{bcrypt}$2a$10$aohc8ylx1YcZx6p/L2BRv.I4oQfDin9Ed2CNTy0ZXQ3ZpdiMalLp6', true);
INSERT INTO authorities (username, authority) VALUES ('john', 'ROLE_USER');
INSERT INTO authorities (username, authority) VALUES ('john', 'ROLE_ADMIN');
Application
package se.mkk.springboot3basicjdbc;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.HeaderWriterLogoutHandler;
import org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter;
import org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter.Directive;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class BasicJdbcSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http //
.sessionManagement(
// https://docs.spring.io/spring-security/reference/servlet/authentication/session-management.html
// https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.application-properties.server
Customizer.withDefaults())
.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests //
.requestMatchers("/login", "/logout").permitAll() //
.anyRequest().authenticated()) //
.httpBasic( //
// https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/basic.html
Customizer.withDefaults()) //
.csrf(csrf -> csrf //
// https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-token-repository-cookie
.ignoringRequestMatchers("/logout", "/api"))
.logout(logout -> logout //
// https://docs.spring.io/spring-security/reference/servlet/authentication/logout.html#clear-all-site-data
.addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directive.ALL))));
return http.build();
}
// https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/jdbc.html
@Bean
public UserDetailsManager jdbcUserDetailsManager(DataSource dataSource) {
JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
return users;
}
}
package se.mkk.springboot3basicjdbc;
import java.security.Principal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "http://localhost:4200")
public class UserController {
@GetMapping
public Map<String, String> getUser(HttpServletRequest request, HttpSession session, Principal principal) {
Map<String, String> rtn = new LinkedHashMap<>();
rtn.put("session_getMaxInactiveInterval_sec", session.getMaxInactiveInterval() + "s");
rtn.put("session_getLastAccessedTime",
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").format(new Date(session.getLastAccessedTime())));
rtn.put("request_getRemoteUser", request.getRemoteUser());
rtn.put("request_isUserInRole_USER", Boolean.toString(request.isUserInRole("USER")));
rtn.put("request_getUserPrincipal_getClass", request.getUserPrincipal().getClass().getName());
rtn.put("principal_getClass_getName", principal.getClass().getName());
rtn.put("principal_getName", principal.getName());
if (principal instanceof Authentication authentication) {
List<String> authorities = authentication.getAuthorities().stream()
.map(grantedAuthority -> grantedAuthority.getAuthority()).toList();
rtn.put("JwtAuthenticationToken.getAuthorities()", authorities.toString());
}
return rtn;
}
}
Test
$ curl -v -X GET -u "john:changeit" http://localhost:8080/api/users
Note: Unnecessary use of -X or --request, GET is already inferred.
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
* Server auth using Basic with user 'john'
> GET /api/users HTTP/1.1
> Host: localhost:8080
> Authorization: Basic am9objpjaGFuZ2VpdA==
> User-Agent: curl/7.85.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Set-Cookie: JSESSIONID=56FAD7EB738EB12B558F02EBC04122BE; Max-Age=180; Expires=Tue, 29 Aug 2023 21:02:41 GMT; Path=/; Secure; HttpOnly; SameSite=Strict
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 0
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Tue, 29 Aug 2023 20:59:41 GMT
<
* Connection #0 to host localhost left intact
{"session_getMaxInactiveInterval_sec":"180s","session_getLastAccessedTime":"2023-08-29 22:59:41 +0200","request_getRemoteUser":"john","request_isUserInRole_USER":"true","request_getUserPrincipal_getClass":"org.springframework.security.authentication.UsernamePasswordAuthenticationToken","principal_getClass_getName":"org.springframework.security.authentication.UsernamePasswordAuthenticationToken","principal_getName":"john","JwtAuthenticationToken.getAuthorities()":"[ROLE_ADMIN, ROLE_USER]"}
$ curl -s -X GET -u "john:changeit" http://localhost:8080/api/users | jq -r
{
"session_getMaxInactiveInterval_sec": "180s",
"session_getLastAccessedTime": "2023-08-29 22:58:32 +0200",
"request_getRemoteUser": "john",
"request_isUserInRole_USER": "true",
"request_getUserPrincipal_getClass": "org.springframework.security.authentication.UsernamePasswordAuthenticationToken",
"principal_getClass_getName": "org.springframework.security.authentication.UsernamePasswordAuthenticationToken",
"principal_getName": "john",
"JwtAuthenticationToken.getAuthorities()": "[ROLE_ADMIN, ROLE_USER]"
}
No comments:
Post a Comment