{"id":19656221,"url":"https://github.com/daggerok/spring-security-basics","last_synced_at":"2025-04-30T20:48:51.447Z","repository":{"id":151042133,"uuid":"257014684","full_name":"daggerok/spring-security-basics","owner":"daggerok","description":"Learn Spring Security by baby steps from zero to pro!","archived":false,"fork":false,"pushed_at":"2020-04-25T13:21:36.000Z","size":128,"stargazers_count":5,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-30T19:51:12.273Z","etag":null,"topics":["build-helper","build-helper-maven-plugin","github-actions-docker","github-actions-java","github-actions-javascript","github-actions-nodejs","release","release-automation","releases","spring-security","spring-security-5","spring-security-example","spring-security-web"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/daggerok.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-19T14:04:39.000Z","updated_at":"2025-03-15T16:52:59.000Z","dependencies_parsed_at":null,"dependency_job_id":"e395095a-595c-4d08-8506-7756f4ff8857","html_url":"https://github.com/daggerok/spring-security-basics","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daggerok%2Fspring-security-basics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daggerok%2Fspring-security-basics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daggerok%2Fspring-security-basics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daggerok%2Fspring-security-basics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/daggerok","download_url":"https://codeload.github.com/daggerok/spring-security-basics/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251781636,"owners_count":21642971,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["build-helper","build-helper-maven-plugin","github-actions-docker","github-actions-java","github-actions-javascript","github-actions-nodejs","release","release-automation","releases","spring-security","spring-security-5","spring-security-example","spring-security-web"],"created_at":"2024-11-11T15:27:05.296Z","updated_at":"2025-04-30T20:48:51.428Z","avatar_url":"https://github.com/daggerok.png","language":"Java","readme":"# spring-security-basics [![CI](https://github.com/daggerok/spring-security-basics/workflows/CI/badge.svg)](https://github.com/daggerok/spring-security-basics/actions?query=workflow%3ACI)\nLearn Spring Security by baby steps from zero to pro! (Status: IN PROGRESS)\n\n## Table of Content\n\n* [Step 0: No security](#step-0)\n* [Step 1: Add authentication](#step-1)\n* [Step 2: Custom authentication](#step-2)\n* [Step 3: Add authorization](#step-3)\n* [Step 4: JavaEE and Spring Security](#step-4)\n* [Step 5.1: JDBC authentication](#step-51)\n* [Step 5.2: Spring Data JDBC authentication](#step-52)\n* [Step 5.3: Spring Data JPA authentication](#step-53)\n* [Step 6: Spring LDAP Security](#step-6)\n* [Versioning and releasing](#maven)\n* [Resources and used links](#resources)\n\n## step: 0\n\nlet's use simple spring boot web app without security at all!\n\n### application\n\nuse needed dependencies in `pom.xml` file:\n\n```xml\n\u003cdependencies\u003e\n  \u003cdependency\u003e\n    \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n    \u003cartifactId\u003espring-boot-starter-web\u003c/artifactId\u003e\n  \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n\nadd in `Application.java` file controller for index page:\n\n```java\n@Controller\nclass IndexPage {\n\n  @GetMapping(\"/\")\n  String index() {\n    return \"index.html\";\n  }\n}\n```\n\ndo not forget about `src/main/resources/static/index.html` template file:\n\n```html\n\u003c!doctype html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n  \u003cmeta charset=\"UTF-8\"\u003e\n  \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n  \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\"\u003e\n  \u003ctitle\u003espring-security baby-steps\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003ch1\u003eHello!\u003c/h1\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nfinally, to gracefully shutdown application under test on CI builds,\nadd actuator dependency:\n\n```xml\n\u003cdependencies\u003e\n  \u003cdependency\u003e\n    \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n    \u003cartifactId\u003espring-boot-starter-actuator\u003c/artifactId\u003e\n  \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n\nwith according configurations in `application.yaml` file:\n\n```yaml\nspring:\n  output:\n    ansi:\n      enabled: always\n---\nspring:\n  profiles: ci\nmanagement:\n  endpoint:\n    shutdown:\n      enabled: true\n  endpoints:\n    web:\n      exposure:\n        include: \u003e\n          shutdown\n```\n\nso, you can start application which is supports shutdown, like so:\n\n```bash\njava -jar /path/to/jar --spring.profiles.active=ci\n```\n\n### test application\n\nuse required dependencies:\n\n```xml\n\u003cdependencies\u003e\n  \u003cdependency\u003e\n    \u003cgroupId\u003ecom.codeborne\u003c/groupId\u003e\n    \u003cartifactId\u003eselenide\u003c/artifactId\u003e\n    \u003cscope\u003etest\u003c/scope\u003e\n  \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n\nimplement Selenide test:\n\n```java\n@Log4j2\n@AllArgsConstructor\nclass ApplicationTest extends AbstractTest {\n\n  @Test\n  void test() {\n    open(\"http://127.0.0.1:8080\"); // open home page...\n    var h1 = $(\"h1\");              // find there \u003ch1\u003e tag...\n    log.info(\"h1 html: {}\", h1);\n    h1.shouldBe(exist, visible)    // element should be inside DOM\n      .shouldHave(text(\"hello\"));  // textContent of the tag should\n                                   // contains expected content...\n  }\n}\n```\n\nsee sources for implementation details.\n\nbuild, run test and cleanup:\n\n```bash\n./mvnw -f step-0-application-without-security\njava -jar ./step-0-application-without-security/target/*jar --spring.profiles.active=ci \u0026\n./mvnw -Dgroups=e2e -f step-0-test-application-without-security\nhttp post :8080/actuator/shutdown\n```\n\n## step: 1\n\nin this step we are going to implement simple authentication.\nit's mean everyone who logged in, can access all available\nresources.\n\n### application\n\nadd required dependencies:\n\n```xml\n\u003cdependencies\u003e\n  \u003cdependency\u003e\n    \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n    \u003cartifactId\u003espring-boot-starter-security\u003c/artifactId\u003e\n  \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n\nupdate `application.yaml` configuration with desired user password:\n\n```yaml\nspring:\n  security:\n    user:\n      password: pwd\n```\n\ntune little bit security config to bein able shutdown application with POST:\nwe have to permit it and disable CSRF:\n\n```java\n@EnableWebSecurity\nclass MyWebSecurity extends WebSecurityConfigurerAdapter {\n\n  @Override\n  protected void configure(HttpSecurity http) throws Exception {\n    http.authorizeRequests()\n          .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()\n          .anyRequest().authenticated()\n        .and()\n          .csrf().disable()\n        .formLogin()\n    ;\n  }\n}\n```\n\n### test application\n\nnow, let's update test according to configured security as follows:\n\n```java\n@Log4j2\n@AllArgsConstructor\nclass ApplicationTest extends AbstractTest {\n\n  @Test\n  void test() {\n    open(\"http://127.0.0.1:8080\");\n    // we should be redirected to login page, so lets authenticate!\n    $(\"#username\").setValue(\"user\");\n    $(\"#password\").setValue(\"pwd\").submit();\n    // everything else is with no changes...\n    var h1 = $(\"h1\");\n    log.info(\"h1 html: {}\", h1);\n    h1.shouldBe(exist, visible)\n      .shouldHave(text(\"hello\"));\n  }\n}\n```\n\nbuild, run test and cleanup:\n\n```bash\n./mvnw -f step-0-application-without-security\nSPRING_PROFILES_ACTIVE=ci java -jar ./step-0-application-without-security/target/*jar \u0026\n./mvnw -Dgroups=e2e -f step-0-test-application-without-security\nhttp post :8080/actuator/shutdown\n```\n\n## step: 2\n\nlet's add few users for authorization:\n\n```java\n@EnableWebSecurity\nclass MyWebSecurity extends WebSecurityConfigurerAdapter {\n\n  @Bean\n  PasswordEncoder passwordEncoder() {\n    return PasswordEncoderFactories.createDelegatingPasswordEncoder();\n  }\n\n  @Override\n  protected void configure(AuthenticationManagerBuilder auth) throws Exception {\n    auth.inMemoryAuthentication()\n          .withUser(\"user\")\n            .password(passwordEncoder().encode(\"password\"))\n            .roles(\"USER\")\n            .and()\n          .withUser(\"admin\")\n            .password(passwordEncoder().encode(\"admin\"))\n            .roles(\"USER\", \"ADMIN\")\n        ;\n  }\n\n  // ...\n}\n```\n\nnow we can authenticate with `users`/`password` or `admin`/`admin`\n\n## step: 3\n\nnow let's add authorization, so we can distinguish that different users\nhave access to some resources where others are not!\n\n### application\n\nin next configuration access to `/admin` path:\n\n```java\n@Controller\nclass AdminPage {\n\n  @GetMapping(\"admin\")\n  String index() {\n    return \"admin/index.html\";\n  }\n}\n```\n\nadd `admin/index.html` file:\n\n```html\n\u003c!doctype html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n  \u003cmeta charset=\"UTF-8\"\u003e\n  \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n  \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\"\u003e\n  \u003ctitle\u003eAdmin Page | spring-security baby-steps\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003ch2\u003eAdministration page\u003c/h2\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nwe can allow to users with admin role:\n\n```java\n@EnableWebSecurity\nclass MyWebSecurity extends WebSecurityConfigurerAdapter {\n\n  @Override\n  protected void configure(HttpSecurity http) throws Exception {\n    http.authorizeRequests()\n          .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()\n          .antMatchers(\"/admin/**\").hasRole(\"ADMIN\")\n          .anyRequest().authenticated()\n        .and()\n          .csrf().disable()\n        .formLogin()\n    ;\n  }\n}\n``` \n\n### test application\n\n```java\n@Value\n@ConstructorBinding\n@ConfigurationProperties(\"test-application-props\")\nclass TestApplicationProps {\n\n  String baseUrl;\n  User admin;\n  User user;\n\n  @Value\n  @ConstructorBinding\n  static class User {\n    String username;\n    String password;\n  }\n}\n\n@Log4j2\n@Tag(\"e2e\")\n@AllArgsConstructor\n@SpringBootTest(properties = {\n    \"test-application-props.user.username=user\",\n    \"test-application-props.user.password=password\",\n    \"test-application-props.admin.username=admin\",\n    \"test-application-props.admin.password=admin\",\n    \"test-application-props.base-url=http://127.0.0.1:8080\",\n})\nclass ApplicationTest {\n\n  ApplicationContext context;\n\n  @Test\n  void admin_should_authorize() {\n    var props = context.getBean(TestApplicationProps.class);\n    open(String.format(\"%s/admin\", props.getBaseUrl()));\n    $(\"#username\").setValue(props.getAdmin().getUsername());\n    $(\"#password\").setValue(props.getAdmin().getPassword()).submit();\n\n    var h2 = $(\"h2\");\n    log.info(\"h2 html: {}\", h2);\n    h2.shouldBe(exist, visible)\n      .shouldHave(text(\"administration\"));\n  }\n\n  @Test\n  void test_forbidden_403() {\n    var props = context.getBean(TestApplicationProps.class);\n    open(String.format(\"%s/admin\", props.getBaseUrl()));\n    $(\"#username\").setValue(props.getUser().getUsername());\n    $(\"#password\").setValue(props.getUser().getPassword()).submit();\n    $(withText(\"403\")).shouldBe(exist, visible);\n    $(withText(\"Forbidden\")).shouldBe(exist, visible);\n  }\n\n  @AfterEach\n  void after() {\n    closeWebDriver();\n  }\n}\n```\n\n## step: 4\n\nlet's try use Spring Security together with JavaEE!\nNOTE: use spring version 4.x, not 5!\n\nin this step we will configure JavaEE app for next\nsets of security rules:\n\nallowed for all: `/`, `/favicon.ico`, `/api/health`, `/login`, `/logout`\nallowed for admins only: `/admin`\nall other paths allowed only for authenticated users.\n\n### application\n\ndependencies:\n\n```xml\n\u003cdependencies\u003e\n  \u003cdependency\u003e\n    \u003cgroupId\u003eorg.springframework.security\u003c/groupId\u003e\n    \u003cartifactId\u003espring-security-config\u003c/artifactId\u003e\n  \u003c/dependency\u003e\n  \u003cdependency\u003e\n    \u003cgroupId\u003eorg.springframework.security\u003c/groupId\u003e\n    \u003cartifactId\u003espring-security-taglibs\u003c/artifactId\u003e\n  \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n\nJAX-RS application:\n\n```java\n@ApplicationScoped\n@ApplicationPath(\"api\")\npublic class Config extends Application { }\n\n@Path(\"\")\n@RequestScoped\n@Produces(APPLICATION_JSON)\npublic class HealthResource {\n\n  @GET\n  @Path(\"health\")\n  public JsonObject hello() {\n    return Json.createObjectBuilder()\n               .add(\"status\", \"UP\")\n               .build();\n  }\n}\n\n@Path(\"v1\")\n@RequestScoped\n@Produces(APPLICATION_JSON)\npublic class MyResource {\n\n  @GET\n  @Path(\"hello\")\n  public JsonObject hello() {\n    return Json.createObjectBuilder()\n               .add(\"hello\", \"world!\")\n               .build();\n  }\n}\n```\n\nSpring Security configuration:\n\n```java\n@Configuration\n@EnableWebSecurity\npublic class SpringSecurityConfig extends WebSecurityConfigurerAdapter {\n\n  @Override\n  protected void configure(AuthenticationManagerBuilder auth) throws Exception {\n    // @formatter:off\n    auth.inMemoryAuthentication()\n          .withUser(\"user\")\n          .password(\"password\")\n          .roles(\"USER\")\n        .and()\n          .withUser(\"admin\")\n          .password(\"admin\")\n          .roles(\"ADMIN\")\n    // @formatter:on\n    ;\n  }\n\n  @Override\n  protected void configure(HttpSecurity http) throws Exception {\n    // @formatter:off\n    http.authorizeRequests()\n          .antMatchers(\"/\", \"/favicon.ico\", \"/api/health\").permitAll()\n          .antMatchers(\"/admin/**\").hasRole(\"ADMIN\")\n          .anyRequest().authenticated()\n        .and()\n          .formLogin()\n        .and()\n          .logout()\n            .logoutSuccessUrl(\"/\")\n            .clearAuthentication(true)\n            .invalidateHttpSession(true)\n            .deleteCookies(\"JSESSIONID\")\n        .and()\n          .csrf().disable()\n        .sessionManagement()\n          .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);\n    // @formatter:on\n    ;\n  }\n}\n\npublic class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {\n\n  public SecurityWebApplicationInitializer() {\n    super(SpringSecurityConfig.class);\n  }\n}\n```\n\nadd `src/main/resources/META-INF/beans.xml` file:\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cbeans xmlns=\"http://xmlns.jcp.org/xml/ns/javaee\"\n       xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n       xsi:schemaLocation=\"http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd\"\n       bean-discovery-mode=\"annotated\"\u003e\n\u003c/beans\u003e\n```\n\nfinally, add HTML pages:\n\nfile `src/main/webapp/index.html`:\n\n```html\n\u003c!doctype html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n  \u003ctitle\u003eHello!\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n  \u003ch1\u003eHello!\u003c/h1\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nfile `src/main/webapp/admin/index.html`:\n\n```html\n\u003c!doctype html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n  \u003ctitle\u003eAdmin\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n  \u003ch1\u003eAdmin page\u003c/h1\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n### test application\n\n```bash\n./mvnw -f step-4-java-ee-jaxrs-jboss-spring-security\n./mvnw -f step-4-java-ee-jaxrs-jboss-spring-security docker:build docker:start\n./mvnw -f step-4-test-java-ee-jboss-spring-security -Dgroups=e2e\n./mvnw -f step-4-java-ee-jaxrs-jboss-spring-security docker:stop docker:remove\n```\n\n## step: 5.1\n\nlet's use jdbc database as users / roles store.\n\nsecurity config:\n\n```java\n@EnableWebSecurity\n@RequiredArgsConstructor\nclass MyWebSecurity extends WebSecurityConfigurerAdapter {\n\n  final DataSource dataSource;\n\n  @Override\n  protected void configure(AuthenticationManagerBuilder auth) throws Exception {\n    auth.jdbcAuthentication()\n          .dataSource(dataSource)\n          .usersByUsernameQuery(\n             \" select sec_username, sec_password, sec_enabled \" +\n             \" from sec_users where sec_username=?            \"\n          )\n          .authoritiesByUsernameQuery(\n            \" select sec_username, sec_authority        \" +\n            \" from sec_authorities where sec_username=? \"\n          );\n    ;\n  }\n\n  // ...\n}\n```\n\nsql schema and data:\n\n```sql\ndrop index if exists sec_authorities_idx;\ndrop table if exists sec_authorities;\ndrop table if exists sec_users;\ndrop schema if exists \"public\";\n\ncreate schema \"public\";\n\ncreate table sec_users (\n  sec_username varchar(255) not null primary key,\n  sec_password varchar(1024) not null,\n  sec_enabled boolean not null\n);\n\ncreate table sec_authorities (\n  sec_username varchar(255) not null,\n  sec_authority varchar(255) not null,\n  constraint sec_authorities_fk\n    foreign key (sec_username)\n      references sec_users (sec_username)\n);\n\ncreate unique index sec_authorities_idx\n  on sec_authorities (sec_username, sec_authority);\n\ninsert into sec_users (sec_username, sec_password, sec_enabled)\nvalues ('user', '{bcrypt}$2a$10$OlBp2JOK0/8xDjiVqh4OYOggr3tHTKfBcv82dso4fsnUPo66f5Ury', true),  -- password\n       ('admin', '{bcrypt}$2a$10$OKPak8tw3jYSyqil/eNKz.U1nF/HtabOotUqi2ceeLuWdBsejH9yS', true); -- admin\ninsert into sec_authorities (sec_username, sec_authority)\nvalues ('user', 'ROLE_USER'),\n       ('admin', 'ROLE_ADMIN');\n```\n\ntesting:\n\n```bash\n./mvnw -f step-5-jdbc-authentication clean package spring-boot:build-image docker-compose:up\nwhile ! [[ `curl -s -o /dev/null -w \"%{http_code}\" 0:8080/actuator/health` -eq 200 ]] ; do sleep 1s ; echo -n '.' ; done\n./mvnw -f step-5-test-jdbc -Dgroups=e2e \n./mvnw -f step-5-jdbc-authentication docker-compose:down\n```\n\n## step: 5.2\n\nlet's use spring-data-jdbc database as users / roles store.\n\nadd security entity, repository and service:\n\n```java\n@With\n@Value\n@Table(\"sec_users\")\nclass Security {\n\n  @Id\n  @Column(\"sec_username\") String username;\n  @Column(\"sec_password\") String password;\n  @Column(\"sec_enabled\") boolean active;\n  @Column(\"sec_authority\") String authority;\n\n  public UserDetails toUserDetails() {\n    return User.builder()\n               .username(username)\n               .password(password)\n               .disabled(!active)\n               .accountExpired(!active)\n               .credentialsExpired(!active)\n               .authorities(AuthorityUtils.createAuthorityList(authority))\n               .build();\n  }\n}\n\ninterface SecurityRepository extends CrudRepository\u003cSecurity, String\u003e {\n\n  @Query(\"select * from sec_users where sec_username = :username limit 1\")\n  Optional\u003cSecurity\u003e findFirstByUsername(@Param(\"username\") String username);\n}\n\n@Service\n@RequiredArgsConstructor\nclass SecurityService implements UserDetailsService {\n\n  final SecurityRepository securityRepository;\n\n  @Override\n  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {\n    return securityRepository.findFirstByUsername(username)\n                             .map(Security::toUserDetails)\n                             .orElseThrow(() -\u003e new UsernameNotFoundException(\n                                 String.format(\"User %s not found.\", username)));\n  }\n}\n```\n\nsecurity config:\n\n```java\n@EnableWebSecurity\n@RequiredArgsConstructor\nclass MyWebSecurity extends WebSecurityConfigurerAdapter {\n\n  final SecurityService securityService;\n\n  @Override\n  protected void configure(AuthenticationManagerBuilder auth) throws Exception {\n    auth.userDetailsService(securityService);\n  }\n\n  // ...\n}\n```\n\nsql schema and data:\n\n```sql\ndrop index if exists sec_users_authorities_idx;\ndrop table if exists sec_users;\ndrop schema if exists \"public\";\n\ncreate schema \"public\";\n\ncreate table sec_users (\n  sec_username varchar(255) not null primary key,\n  sec_password varchar(1024) not null,\n  sec_enabled boolean not null,\n  sec_authority varchar(255) not null\n);\n\ncreate unique index sec_users_authorities_idx\n  on sec_users (sec_username, sec_authority);\n\ninsert into sec_users (sec_username, sec_password, sec_enabled, sec_authority)\nvalues ('user', '{bcrypt}$2a$10$OlBp2JOK0/8xDjiVqh4OYOggr3tHTKfBcv82dso4fsnUPo66f5Ury', true, 'ROLE_USER')\n,      ('admin', '{bcrypt}$2a$10$OKPak8tw3jYSyqil/eNKz.U1nF/HtabOotUqi2ceeLuWdBsejH9yS', true, 'ROLE_ADMIN')\n;\n```\n\ntesting:\n\n```bash\n./mvnw -f step-5-spring-data-jdbc-authentication clean package spring-boot:build-image docker-compose:up\n./mvnw -f step-5-test-jdbc -Dgroups=e2e \n./mvnw -f step-5-spring-data-jdbc-authentication docker-compose:down\n```\n\n## step: 5.3\n\nlet's use spring-data-jpa this time.\n\nrequired changes:\n\n```java\n@Data\n@Entity\n@Setter(PROTECTED)\n@NoArgsConstructor(access = PROTECTED)\n@AllArgsConstructor(staticName = \"of\")\n@Table(name = \"sec_users\")\nclass Security {\n\n  @Id\n  @Column(nullable = false, name = \"sec_username\")\n  private String username;\n\n  @Column(nullable = false, name = \"sec_password\")\n  private String password;\n\n  @Column(nullable = false, name = \"sec_enabled\")\n  private boolean active;\n\n  @Column(nullable = false, name = \"sec_authority\")\n  private String authority;\n\n  public UserDetails toUserDetails() {\n    return User.builder()\n               .username(username)\n               .password(password)\n               .disabled(!active)\n               .accountExpired(!active)\n               .credentialsExpired(!active)\n               .authorities(AuthorityUtils.createAuthorityList(authority))\n               .build();\n  }\n}\n\ninterface SecurityRepository extends CrudRepository\u003cSecurity, String\u003e {\n\n  @Query\n  Optional\u003cSecurity\u003e findFirstByUsername(@Param(\"username\") String username);\n}\n\n@Service\n@RequiredArgsConstructor\nclass SecurityService implements UserDetailsService {\n\n  final SecurityRepository securityRepository;\n\n  @Override\n  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {\n    return securityRepository.findFirstByUsername(username)\n                             .map(Security::toUserDetails)\n                             .orElseThrow(() -\u003e new UsernameNotFoundException(\n                                 String.format(\"User %s not found.\", username)));\n  }\n}\n\n@EnableWebSecurity\n@RequiredArgsConstructor\nclass MyWebSecurity extends WebSecurityConfigurerAdapter {\n\n  final SecurityService securityService;\n\n  @Bean\n  PasswordEncoder passwordEncoder() {\n    return PasswordEncoderFactories.createDelegatingPasswordEncoder();\n  }\n\n  @Override\n  protected void configure(AuthenticationManagerBuilder auth) throws Exception {\n    auth.userDetailsService(securityService);\n  }\n\n  @Override\n  protected void configure(HttpSecurity http) throws Exception {\n    http.authorizeRequests()\n          .requestMatchers(EndpointRequest.to(\"health\", \"shutdown\")).permitAll()\n          .antMatchers(\"/\", \"/favicon.ico\", \"/assets/**\").permitAll()\n          .antMatchers(\"/admin/**\").hasRole(\"ADMIN\")\n          .anyRequest().authenticated()\n        .and()\n          .csrf().disable()\n        .formLogin()\n          .and()\n        .httpBasic()\n    ;\n  }\n}\n```\n\n_application.yaml` file:\n\n```yaml\nspring:\n  datasource:\n    driver-class-name: org.postgresql.Driver\n    url: jdbc:postgresql://${POSTGRES_HOST:127.0.0.1}:${POSTGRES_PORT:5432}/${POSTGRES_DB:postgres}\n    username: ${POSTGRES_USER:postgres}\n    password: ${POSTGRES_PASSWORD:postgres}\n  flyway:\n    enabled: true\n  jpa:\n    database: postgresql\n    generate-ddl: false\n    show-sql: true\n    hibernate:\n      ddl-auto: validate\n    properties:\n      hibernate:\n        temp:\n          use_jdbc_metadata_defaults: false\n```\n\n`db/migration` scripts:\n\n```sql\ncreate table sec_users (\n  sec_username varchar(255) not null primary key,\n  sec_password varchar(1024) not null,\n  sec_enabled boolean not null,\n  sec_authority varchar(255) not null\n)\n;\ncreate unique index sec_users_authorities_idx\n  on sec_users (sec_username, sec_authority)\n;\ninsert into sec_users (sec_username, sec_password, sec_enabled, sec_authority)\nvalues ('user', '{bcrypt}$2a$10$OlBp2JOK0/8xDjiVqh4OYOggr3tHTKfBcv82dso4fsnUPo66f5Ury', true, 'ROLE_USER')\n,      ('admin', '{bcrypt}$2a$10$OKPak8tw3jYSyqil/eNKz.U1nF/HtabOotUqi2ceeLuWdBsejH9yS', true, 'ROLE_ADMIN')\n;\n```\n\ntesting:\n\n```bash\n# docker-compose -f step-5-spring-data-jpa-authentication/docker-compose.yaml up postgres\n./mvnw -f step-5-spring-data-jpa-authentication clean package spring-boot:build-image docker-compose:up\n./mvnw -f step-5-test-jdbc -Dgroups=e2e \n./mvnw -f step-5-spring-data-jpa-authentication docker-compose:down\n```\n\n\u003c!--\n\n## step: 6\n\nlet's implement organizational security authentication (not authorization) by using LDAP catalog as auth storage.\n\ninstall LDAP using `ldap/Dockerfile`:\n\n```Dockerfile\nFROM osixia/openldap-backup:1.3.0\nLABEL MAINTAINER=\"Maksim Kostromin \u003cdaggerok@gmail.com\u003e https://githuib.com/daggerok/spring-data-ldap-example\"\nENTRYPOINT [\"/bin/bash\"]\nCMD [\"-c\", \"/container/tool/run --copy-service -l debug\"]\nCOPY --chown=openldap:openldap ./test-data.ldif /container/service/slapd/assets/config/bootstrap/ldif/50-test-data.ldif\n```\n\nit's testdata contains in `ldap/test-data.ldif` file:\n\n```ldap\nversion: 1\n\n# Entry 3: uid=user,dc=my-test-company-domain,dc=com\n# user: uid=user,dc=my-test-company-domain,dc=com\n# password: password\n\ndn: uid=user,dc=my-test-company-domain,dc=com\nuid: user\ncn: user\nsn: 3\ndescription: My Test Company LDAP user organization account\nobjectclass: top\nobjectClass: inetOrgPerson\nmail: user@my-test-company-domain.com\nuserPassword: password\n\n## Entries already exists / provided by docker container:\n#\n## Entry 1: dc=my-test-company-domain,dc=com\n#dn: dc=my-test-company-domain,dc=com\n#dc: my-test-company-domain\n#o: My Test Company Inc.\n#objectclass: top\n#objectclass: dcObject\n#objectclass: organization\n#\n## Entry 2: cn=admin,dc=my-test-company-domain,dc=com\n#dn: cn=admin,dc=my-test-company-domain,dc=com\n#cn: admin\n#description: My Test Company LDAP administrator\n#objectclass: simpleSecurityObject\n#objectclass: organizationalRole\n#userpassword: adm1nZupperUberP@assw0rd!!1111oneoneone\n## user: cn=admin,dc=my-test-company-domain,dc=com\n```\n\ncompose everything in `docker.yaml` file:\n\n```yaml\nversion: '2.1'\nnetworks:\n  backend-network:\n  frontend-network:\nservices:\n  ldap:\n    hostname: ldap.my-test-company-domain.com\n    build: ./ldap\n    environment:\n      LDAP_ORGANISATION: My Test Company Inc.\n      LDAP_DOMAIN: my-test-company-domain.com\n      LDAP_BASE_DN: dc=my-test-company-domain,dc=com\n      LDAP_ADMIN_PASSWORD: adm1nZupperUberP@assw0rd!!1111oneoneone\n      LDAP_CONFIG_PASSWORD: config\n      LDAP_RFC2307BIS_SCHEMA: 'false'\n      LDAP_READONLY_USER: 'false'\n      LDAP_READONLY_USER_USERNAME: readonly\n      LDAP_READONLY_USER_PASSWORD: readonly\n      LDAP_BACKUP_CONFIG_CRON_EXP: '* * * * *'\n      LDAP_BACKUP_DATA_CRON_EXP: '*/15 * * * *'\n      LDAP_BACKUP_TTL: 7\n    ports:\n    - '389:389'\n    - '636:636'\n    networks:\n      backend-network:\n        aliases:\n        - ldap\n        - ldap.backend-network\n        - ldap.my-test-company-domain.com\n    healthcheck:\n      test: ( ( test 1 -eq `ss -tulwn | grep '0.0.0.0:389' | wc -l` ) \u0026\u0026 ( test 1 -eq `ss -tulwn | grep '0.0.0.0:636' | wc -l` ) ) || exit 1\n      interval: 5s\n      timeout: 5s\n      retries: 55\n  ldap-admin-ui:\n    image: osixia/phpldapadmin:0.9.0\n    depends_on:\n      ldap:\n        condition: service_healthy\n    environment:\n      PHPLDAPADMIN_LDAP_HOSTS: ldap.my-test-company-domain.com\n      LDAP_USER: cn=admin,dc=my-test-company-domain,dc=com\n      LDAP_PASSWORD: adm1nZupperUberP@assw0rd!!1111oneoneone\n    ports:\n    - '80:80'\n    - '443:443'\n    networks:\n    - backend-network\n    - frontend-network\n  frontend:\n    image: daggerok/spring-security-basics-step-6-spring-ldap-security\n    depends_on:\n      ldap:\n        condition: service_healthy\n    environment:\n      LDAP_HOST: ldap.my-test-company-domain.com\n      LDAP_BASE_DN: dc=my-test-company-domain,dc=com\n      LDAP_USER: cn=admin,dc=my-test-company-domain,dc=com\n      LDAP_PASSWORD: adm1nZupperUberP@assw0rd!!1111oneoneone\n    networks:\n    - backend-network\n    - frontend-network\n```\n\nle's test:\n\n```bash\n./mvnw -f step-6-spring-ldap-security package spring-boot:build-image docker-compose:up\n```\n\n--\u003e\n\n## maven\n\nwe will be releasing after each important step! so it will be easy simply checkout needed version from git tag.\n\nrelease version without maven-release-plugin (when you aren't using *-SNAPSHOT version for development):\n\n```bash\ncurrentVersion=`./mvnw -q --non-recursive exec:exec -Dexec.executable=echo -Dexec.args='${project.version}'`\ngit tag \"v$currentVersion\"\n\n./mvnw build-helper:parse-version -DgenerateBackupPoms=false -DgenerateBackupPoms=false versions:set \\\n  -DnewVersion=\\${parsedVersion.majorVersion}.\\${parsedVersion.minorVersion}.\\${parsedVersion.nextIncrementalVersion} \\\n  -f step-4-java-ee-jaxrs-jboss-spring-security\n./mvnw build-helper:parse-version -DgenerateBackupPoms=false versions:set -DgenerateBackupPoms=false \\\n  -DnewVersion=\\${parsedVersion.majorVersion}.\\${parsedVersion.minorVersion}.\\${parsedVersion.nextIncrementalVersion}\nnextVersion=`./mvnw -q --non-recursive exec:exec -Dexec.executable=echo -Dexec.args='${project.version}'`\n\ngit add . ; git commit -am \"v$currentVersion release.\" ; git push --tags\n```\n\nincrement version:\n\n```bash\n1.1.1?-\u003e1.1.2\n./mvnw build-helper:parse-version -DgenerateBackupPoms=false versions:set -DgenerateBackupPoms=false -DnewVersion=\\${parsedVersion.majorVersion}.\\${parsedVersion.minorVersion}.\\${parsedVersion.nextIncrementalVersion}\n```\n\ncurrent release version:\n\n```bash\n# 1.2.3-SNAPSHOT -\u003e 1.2.3\n./mvnw build-helper:parse-version -DgenerateBackupPoms=false versions:set -DgenerateBackupPoms=false -DnewVersion=\\${parsedVersion.majorVersion}.\\${parsedVersion.minorVersion}.\\${parsedVersion.incrementalVersion}\n```\n\nnext snapshot version:\n\n```bash\n# 1.2.3? -\u003e 1.2.4-SNAPSHOT\n./mvnw build-helper:parse-version -DgenerateBackupPoms=false versions:set -DgenerateBackupPoms=false -DnewVersion=\\${parsedVersion.majorVersion}.\\${parsedVersion.minorVersion}.\\${parsedVersion.nextIncrementalVersion}-SNAPSHOT\n```\n\n## resources\n\n* [Spring Security LDAP Authentication](https://spring.io/guides/gs/authenticating-ldap/)\n* [Modern Spring Security for Spring Actuator endpoints](https://youtu.be/SSu7V-S5yec?t=520)\n* [YouTube: Spring Security Basics](https://www.youtube.com/playlist?list=PLqq-6Pq4lTTYTEooakHchTGglSvkZAjnE)\n* https://github.com/daggerok/spring-security-examples\n\u003c!--\n* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html)\n* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.3.0.M4/maven-plugin/reference/html/)\n* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.3.0.M4/maven-plugin/reference/html/#build-image)\n* [Spring Security](https://docs.spring.io/spring-boot/docs/2.2.6.RELEASE/reference/htmlsingle/#boot-features-security)\n* [Spring Configuration Processor](https://docs.spring.io/spring-boot/docs/2.2.6.RELEASE/reference/htmlsingle/#configuration-metadata-annotation-processor)\n* [Spring Web](https://docs.spring.io/spring-boot/docs/2.2.6.RELEASE/reference/htmlsingle/#boot-features-developing-web-applications)\n* [Securing a Web Application](https://spring.io/guides/gs/securing-web/)\n* [Spring Boot and OAuth2](https://spring.io/guides/tutorials/spring-boot-oauth2/)\n* [Authenticating a User with LDAP](https://spring.io/guides/gs/authenticating-ldap/)\n* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/)\n* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/)\n* [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/)\n--\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaggerok%2Fspring-security-basics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdaggerok%2Fspring-security-basics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaggerok%2Fspring-security-basics/lists"}