Skip to content

Commit

Permalink
feat: Use DB backed UserDetailsService
Browse files Browse the repository at this point in the history
  • Loading branch information
beo1975 committed Nov 13, 2024
1 parent 819c7c8 commit 6bf8041
Show file tree
Hide file tree
Showing 13 changed files with 124 additions and 19 deletions.
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import org.springframework.boot.gradle.tasks.bundling.BootJar
import org.springframework.boot.gradle.tasks.run.BootRun

plugins {
java
Expand Down Expand Up @@ -50,3 +51,5 @@ jte {
springBoot { buildInfo() }

tasks.getByName<BootJar>("bootJar") { this.archiveFileName.set("dictionary-learning-platform.jar") }

tasks.getByName<BootRun>("bootTestRun") { this.jvmArgs = listOf("-Dspring.profiles.active=dev") }
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package com.dictionary.learning.platform.config;

import com.dictionary.learning.platform.user.AppUserDetailsService;
import com.dictionary.learning.platform.user.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
Expand All @@ -28,7 +29,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws
.formLogin(login -> login.loginPage(LOGIN_PAGE)
.defaultSuccessUrl("/learning")
.permitAll())
.logout(logout -> logout.logoutSuccessUrl(LOGIN_PAGE))
.logout(logout -> logout.logoutSuccessUrl(LOGIN_PAGE).deleteCookies("JSESSIONID"))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.maximumSessions(1));

Expand All @@ -37,16 +38,11 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

@Bean
public InMemoryUserDetailsManager userDetailsManager() {
UserDetails userDetails = User.withUsername("admin")
.password(passwordEncoder().encode("admin"))
.roles("ADMIN")
.build();

return new InMemoryUserDetailsManager(userDetails);
public UserDetailsService userDetailsService(@Autowired UserRepository userRepository) {
return new AppUserDetailsService(userRepository);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.dictionary.learning.platform.user;

import java.util.List;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class AppUserDetailsService implements UserDetailsService {

private final UserRepository userRepository;

public AppUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException(username));

List<SimpleGrantedAuthority> authorities = List.of(new SimpleGrantedAuthority(user.getRole()));

return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getPassword(), authorities);
}
}
21 changes: 21 additions & 0 deletions app/src/main/java/com/dictionary/learning/platform/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public class User {
@NotBlank
private String username;

@NotBlank
private String password;

private String role;

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Word> words = new HashSet<>();

Expand All @@ -38,6 +43,22 @@ public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getRole() {
return role;
}

public void setRole(String role) {
this.role = role;
}

public Set<Word> getWords() {
return words;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.dictionary.learning.platform.user;

import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

interface UserRepository extends JpaRepository<User, Long> {}
public interface UserRepository extends JpaRepository<User, Long> {

Optional<User> findByUsername(String userName);
}
2 changes: 1 addition & 1 deletion app/src/main/jte/layout/default.jte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spring Security Demo</title>
<title>Dictionary learning</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100">
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
spring:
flyway:
locations: 'classpath:/dev/db/migration/postgresql'
3 changes: 3 additions & 0 deletions app/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ management:
web:
exposure:
include: info,health,sbom
info:
git:
mode: simple

server:
error:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
CREATE SEQUENCE IF NOT EXISTS user_seq START WITH 10 INCREMENT BY 1;
CREATE SEQUENCE IF NOT EXISTS user_seq START WITH 10 INCREMENT BY 1;

CREATE TABLE users (
id BIGINT NOT NULL,
username VARCHAR(255),
password VARCHAR(255),
role VARCHAR(255),
CONSTRAINT pk_user PRIMARY KEY (id)
);

CREATE SEQUENCE IF NOT EXISTS word_seq START WITH 20 INCREMENT BY 1;
CREATE SEQUENCE IF NOT EXISTS word_seq START WITH 20 INCREMENT BY 1;

CREATE TABLE words (
id BIGINT NOT NULL,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
CREATE SEQUENCE IF NOT EXISTS user_seq START WITH 10 INCREMENT BY 1;

CREATE TABLE users (
id BIGINT NOT NULL,
username VARCHAR(255),
password VARCHAR(255),
role VARCHAR(255),
CONSTRAINT pk_user PRIMARY KEY (id)
);

CREATE SEQUENCE IF NOT EXISTS word_seq START WITH 20 INCREMENT BY 1;

CREATE TABLE words (
id BIGINT NOT NULL,
en VARCHAR(255),
sk VARCHAR(255),
lesson INTEGER NOT NULL,
grade INTEGER NOT NULL,
user_id BIGINT,
CONSTRAINT pk_word PRIMARY KEY (id)
);

ALTER TABLE words ADD CONSTRAINT FK_WORD_ON_USER FOREIGN KEY (user_id) REFERENCES users (id);
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- User
INSERT INTO users (id, username, password, role) VALUES (1, 'jane', '{noop}jane', 'admin');
INSERT INTO users (id, username, password, role) VALUES (2, 'bob', '{noop}bob', 'user');
-- Word
INSERT INTO words (id, en, sk, lesson, grade, user_id) VALUES (1, 'father', 'otec', 1, 1, 1);
INSERT INTO words (id, en, sk, lesson, grade, user_id) VALUES (2, 'mother', 'matka', 1, 1, 1);
INSERT INTO words (id, en, sk, lesson, grade, user_id) VALUES (3, 'brother', 'brat', 1, 1, 1);
INSERT INTO words (id, en, sk, lesson, grade, user_id) VALUES (4, 'sister', 'sestra', 1, 1, 1);
INSERT INTO words (id, en, sk, lesson, grade, user_id) VALUES (5, 'water', 'voda', 1, 2, 1);
INSERT INTO words (id, en, sk, lesson, grade, user_id) VALUES (6, 'fire', 'oheň', 1, 2, 1);
INSERT INTO words (id, en, sk, lesson, grade, user_id) VALUES (7, 'lion', 'lev', 2, 2, 1);
INSERT INTO words (id, en, sk, lesson, grade, user_id) VALUES (8, 'mom', 'mama', 1, 1, 2);
INSERT INTO words (id, en, sk, lesson, grade, user_id) VALUES (9, 'dad', 'tata', 1, 1, 2);
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import com.dictionary.learning.platform.repository.BaseTestRepository;
import java.util.List;
import java.util.Optional;
import org.assertj.core.api.WithAssertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.jdbc.Sql;

Expand All @@ -19,4 +22,12 @@ void testFindAll() {

assertThat(users).hasSize(2);
}

@ParameterizedTest
@CsvSource({"jane,admin", "bob,user"})
void findByUsername(String username, String role) {
Optional<User> user = repository.findByUsername(username);

assertThat(user).hasValueSatisfying(u -> assertThat(u.getRole()).isEqualTo(role));
}
}
4 changes: 2 additions & 2 deletions app/src/test/resources/sql/init_db.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-- User
INSERT INTO users (id, username) VALUES (1, 'jane');
INSERT INTO users (id, username) VALUES (2, 'bob');
INSERT INTO users (id, username, password, role) VALUES (1, 'jane', '{noop}jane', 'admin');
INSERT INTO users (id, username, password, role) VALUES (2, 'bob', '{noop}bob', 'user');
-- Word
INSERT INTO words (id, en, sk, lesson, grade, user_id) VALUES (1, 'father', 'otec', 1, 1, 1);
INSERT INTO words (id, en, sk, lesson, grade, user_id) VALUES (2, 'mother', 'matka', 1, 1, 1);
Expand Down

0 comments on commit 6bf8041

Please sign in to comment.