📜 ⬆️ ⬇️

Spring without XML. Part 1

Hi, Habrahabr!

At the moment, there are already quite a few guides on such a bundle, but in my opinion, first of all, they are a bit outdated, and secondly, I think that there should be a guide on how to make something tangible but simple to show that this is possible.

So, if you want to try Spring MVC with saving in the database and 0 (zero) xml-configuration files, please under the cat!
')


Of course, I would like to start the application right away, but first we will prepare a little.

1. Preparing for launch


1.1 IDE


All development will be conducted on Intellij IDEA, but I do not think that the implementation in another IDE will be much more difficult.

1.2 Folder structure


First, create a project folder, call it ForHabrahabr
For our project, in the root you need to create the following folder tree:

directories tree
(you can just do the instructions for the rest in the next section)

1.3 Gradle & Git


For independent
So, we received an application framework.
Now let's add version control and a collector to it.
To do this, add .gitignore to ForHabrahabr with this content:

.gradle
.idea
* .iml
build /

Go to this directory via the console and write
 git init


Now we add bulid.gradle with all dependencies that will be useful to us in the process of writing the application.
build.gradle
buildscript {
repositories {
mavenCentral ()
}
dependencies {
classpath ("org.springframework.boot: spring-boot-gradle-plugin: 1.2.5.RELEASE")

classpath 'mysql: mysql-connector-java: 5.1.34'
}
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

jar {
baseName = 'gs-rest-service'
version = '0.1.0'
}

repositories {
mavenCentral ()
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
compile ("org.springframework.boot: spring-boot-starter-web")
compile ("org.springframework.boot: spring-boot-starter-data-jpa")
compile ("org.springframework.boot: spring-boot-starter-security")
compile ("org.springframework.boot: spring-boot-starter-thymeleaf")
compile 'mysql: mysql-connector-java: 5.1.31'
compile 'commons-dbcp: commons-dbcp: 1.4'
testCompile ("org.springframework: spring-test")
testCompile ("junit: junit")
testCompile 'org.springframework.security:spring-security-test:4.0.1.RELEASE'
}

task wrapper (type: Wrapper) {
gradleVersion = '2.3'
}


Then in the console in the same directory where build.gradle we write
 gradle wrapper
 ./gradlew build    
 (or for windows ./gradlew.bat build) 


Now you can use gradlew.bat / gradlew depending on the OS.

1.3.1 For the rest




1.4 Database


As the database, choose MySQL as the easiest one for quickstart. Create it on localhost,
in it we create the forhabrahabr base, further we will create labels in it
users
roles
users_roles
posts
likes
But more on that later, while it is enough to create a database.

2. Getting Started


2.1 Creating a project


So, first we will open our newly created project in Intellj IDEA, she will see Gradle and offer to use it:
(Welcome to ItelliJ IDEA -> Open -> ForHabrahabr).

image

In this window, just press OK, if it is not there (or problems with the Gradle JVM) - write in HP, I will understand what is wrong.

In the end, should get this project:

image

2.2 Add the first code


First, create a package for all classes, call it habraspring (the usual folder in src / main / java /), and in it is the first
Application class:

Class code
package habraspring; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootApplication @ComponentScan @EnableJpaRepositories(basePackages = {"habraspring"}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 


But in this form, our application will not start yet, you need to show the autoconfigurator where the database is located, for this we will add the file to the resources / application.properties file.

With this content
 #settings for database
 spring.datasource.url = jdbc: mysql: // localhost / forhabrahabr
 spring.datasource.username = root
 spring.datasource.password =
 spring.datasource.driver-class-name = com.mysql.jdbc.Driver
 #turned on to enable lazy loading
 spring.jpa.properties.hibernate.enable_lazy_load_no_trans = true


You also need to create a resources / templates / folder for the template engine.

The resource folder will look like this:

image

Do not pay attention to the .gitkeep files, they are not needed for the program to work, you can safely delete / not create them.

Done, you can start your application for the first time without crashing

To start, you need to run the bootRun task (double click on it):

image

If there is no such panel, go to View -> Tool Windows -> Gradle.

In the application log there will be something like this:

Startup log
 15:24:47: Executing external task 'bootRun' ...
 : compileJava UP-TO-DATE
 : processResources
 : classes
 : findMainClass
 : bootRun

   .  ____ _ __ _ _
  / \\ / ___'_ __ _ _ (_) _ __ __ _ \ \ \ \
 (() \ ___ | '_ |' _ | | '_ \ / _` | \ \ \ \
  \\ / ___) |  | _) |  |  |  |  |  ||  (_ | |))))
   '| ____ |  .__ | _ |  | _ | _ |  | _ \ __, |  / / / /
  ========= | _ | ============== | ___ / = / _ / _ / _ /
  :: Spring Boot :: (v1.2.5.RELEASE)

 2015-07-11 14: 24: 49.180 INFO 12590 --- [main] habraspring. Application: Starting Application on MacBook-Pro-Maksim.local with PID 12590 (/ Users / admin / IdeaProjects / ForHabrahabr / build / classes / main started by admin in / Users / admin / IdeaProjects / ForHabrahabr)
 2015-07-11 14: 24: 49.230 INFO 12590 --- [main] ationConfigEmbeddedWebApplicationContext: Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@2eda0940: startup date [Sat Jul 11 ​​14:24:49 MSK 2015];  root of context hierarchy
 2015-07-11 14: 24: 50.029 INFO 12590 --- [main] osbfsDefaultListableBeanFactory: Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null];  scope =;  abstract = false;  lazyInit = false;  autowireMode = 3;  dependencyCheck = 0;  autowireCandidate = true;  primary = false;  factoryBeanName = org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration $ WhitelabelErrorViewConfiguration;  factoryMethodName = beanNameViewResolver;  initMethodName = null;  destroyMethodName = (inferred);  [org / springframework / boot / autoconfigure / web / ErrorMvcAutoConfiguration $ WhitelabelErrorViewConfiguration.class]] [Root bean: class [null];  scope =;  abstract = false;  lazyInit = false;  autowireMode = 3;  dependencyCheck = 0;  autowireCandidate = true;  primary = false;  factoryBeanName = org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration $ WebMvcAutoConfigurationAdapter;  factoryMethodName = beanNameViewResolver;  initMethodName = null;  destroyMethodName = (inferred);  defined in class path resource [org / springframework / boot / autoconfigure / web / WebMvcAutoConfiguration $ WebMvcAutoConfigurationAdapter.class]]
 2015-07-11 14: 24: 50.701 INFO 12590 --- [main] trationDelegate $ BeanPostProcessorChecker: bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [class org.springframework.transaction.annotation.ProxyTransformat "class $$ 1f1e9ae] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
 2015-07-11 14: 24: 50.727 INFO 12590 --- [main] trationDelegate $ BeanPostProcessorChecker: Bean 'transactionAttributeSource' of type [class org.springframework.transaction.annotation.AnnotationTransactionAttributeSource] is not allowed for example: not eligible for auto-proxying)
 2015-07-11 14: 24: 50.741 INFO 12590 --- [main] trationDelegate $ BeanPostProcessorChecker: Bean 'transactionInterceptor' of type [class org.springframework.transaction.interceptor.TransactionInterceptor] for example: not eligible for auto-proxying)
 2015-07-11 14: 24: 50.746 INFO 12590 --- [main] trationDelegate $ BeanPostProcessorChecker: Bean 'org.springframework.transaction.config.internalTransactionAdvisor' of type [class org.springframework.transaction.interceptor.BeanFactoryTransactionerataerataeraftArte.staframework.transaction.config. eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
 2015-07-11 14: 24: 51.168 INFO 12590 --- [main] sbcetTomcatEmbeddedServletContainer: Tomcat initialized with port (s): 8080 (http)
 2015-07-11 14: 24: 51.408 INFO 12590 --- [main] o.apache.catalina.core.StandardService: Starting service Tomcat
 2015-07-11 14: 24: 51.409 INFO 12590 --- [main] org.apache.catalina.core.StandardEngine: Starting Servlet Engine: Apache Tomcat / 8.0.23
 2015-07-11 14: 24: 51.601 INFO 12590 --- [ost-startStop-1] oaccC [Tomcat]. [Localhost]. [/]: Initializing Spring embedded WebApplicationContext
 2015-07-11 14: 24: 51.601 INFO 12590 --- [ost-startStop-1] osweb.context.ContextLoader: Root WebApplicationContext: initialization completed 2374 ms
 2015-07-11 14: 24: 52.570 INFO 12590 --- [ost-startStop-1] basAuthenticationManagerConfiguration: 

 Using default security password: bd1659e1-4c49-43a2-9fd6-2ca7d46e9e23

 2015-07-11 14: 24: 52.614 INFO 12590 --- [ost-startStop-1] ossweb.DefaultSecurityFilterChain: Creating filter chain: Ant [pattern = '/ css / **'], []
 2015-07-11 14: 24: 52.614 INFO 12590 --- [ost-startStop-1] ossweb.DefaultSecurityFilterChain: Creating filter chain: Ant [pattern = '/ js / **'], []
 2015-07-11 14: 24: 52.614 INFO 12590 --- [ost-startStop-1] ossweb.DefaultSecurityFilterChain: Creating filter chain: Ant [pattern = '/ images / **'], []
 2015-07-11 14: 24: 52.614 INFO 12590 --- [ost-startStop-1] ossweb.DefaultSecurityFilterChain: Creating filter chain: Ant [pattern = '/ ** / favicon.ico'], []
 2015-07-11 14: 24: 52.614 INFO 12590 --- [ost-startStop-1] ossweb.DefaultSecurityFilterChain: Creating filter chain: Ant [pattern = '/ error'], []
 2015-07-11 14: 24: 52.650 INFO 12590 --- [ost-startStop-1] ossweb.DefaultSecurityFilterChain: Creating a filter chain: OrRequestMatcher [requestMatchers = [Ant [pattern = '/ **']]], [org .springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter @ 5854c7d0, org.springframework.security.web.context.SecurityContextPersistenceFilter@874f491, and of up to about 20 nights of max total. .web.authentication.logout.LogoutFilter @ 609329b3, org.springframework.security.web.authentication. .SecurityContextHolderAwareRequestFilter @ a3153e3, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1b8b1dc9, org.springframework.access.access.access.access.access.access.access.access.asn.acquireFileter.ab.Abl.Anl.Anl.  4, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@1fb86c05]
 2015-07-11 14: 24: 52.723 INFO 12590 --- [ost-startStop-1] osbcembedded.FilterRegistrationBean: Mapping filter: 'characterEncodingFilter' to: [/ *]
 2015-07-11 14: 24: 52.724 INFO 12590 --- [ost-startStop-1] osbcembedded.FilterRegistrationBean: Mapping filter: 'hiddenHttpMethodFilter' to: [/ *]
 2015-07-11 14: 24: 52.724 INFO 12590 --- [ost-startStop-1] osbcembedded.FilterRegistrationBean: Mapping filter: 'springSecurityFilterChain' to: [/ *]
 2015-07-11 14: 24: 52.724 INFO 12590 --- [ost-startStop-1] osbceServletRegistrationBean: Mapping servlet: 'dispatcherServlet' to [/]
 2015-07-11 14: 24: 53.410 INFO 12590 --- [main] j.LocalContainerEntityManagerFactoryBean: Building JPA container EntityManagerFactory for persistence unit 'default'
 2015-07-11 14: 24: 53.425 INFO 12590 --- [main] o.hibernate.jpa.internal.util.LogHelper: HHH000204: Processing PersistenceUnitInfo [
	 name: default
	 ...]
 2015-07-11 14: 24: 53.500 INFO 12590 --- [main] org.hibernate.Version: HHH000412: Hibernate Core {4.3.10.Final}
 2015-07-11 14: 24: 53.503 INFO 12590 --- [main] org.hibernate.cfg.Environment: HHH000206: hibernate.properties not found
 2015-07-11 14: 24: 53.505 INFO 12590 --- [main] org.hibernate.cfg.Environment: HHH000021: Bytecode provider name: javassist
 2015-07-11 14: 24: 53.628 INFO 12590 --- [main] o.hibernate.annotations.common.Version: HCANN000001: Hibernate Commons Annotations {4.0.5.Final}
 2015-07-11 14: 24: 53.711 INFO 12590 --- [main] org.hibernate.dialect.Dialect: HHH000400: Using dialect: org.hibernate.dialect.MySQL5Dialect
 2015-07-11 14: 24: 53.774 INFO 12590 --- [main] ohhiast.ASTQueryTranslatorFactory: HHH000397: Using ASTQueryTranslatorFactory
 2015-07-11 14: 24: 54.244 INFO 12590 --- [main] swsmmaRequestMappingHandlerAdapter: Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@2eda0940: launch date [Sat Jul 11 ​​14:24 MSK 2015];  root of context hierarchy
 2015-07-11 14: 24: 54.328 INFO 12590 --- [main] swsmmaRequestMappingHandlerMapping: Mapped "{[/ error]}": java.util.Map <java.lang.String , java.lang.Object >> org.springframework.boot.autoconfigure.web.BasicErrorController.error (javax.servlet.http.HttpServletRequest)
 2015-07-11 14: 24: 54.328 INFO 12590 --- [main] swsmmaRequestMappingHandlerMapping: Mapped "{[/ error], produces = [text / html]}" for public org.springframework.web.servlet.ModelAndView org. springframework.boot.autoconfigure.web.BasicErrorController.errorHtml (javax.servlet.http.HttpServletRequest)
 2015-07-11 14: 24: 54.356 INFO 12590 --- [main] oswshandler.SimpleUrlHandlerMapping: Mapped URL path [/ webjars / **] for class of [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
 2015-07-11 14: 24: 54.357 INFO 12590 --- [main] oswshandler.SimpleUrlHandlerMapping: Mapped URL path [/ **] for handler type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
 2015-07-11 14: 24: 54.393 INFO 12590 --- [main] oswshandler.SimpleUrlHandlerMapping: Mapped URL path [/**/favicon.ico] [class org.springframework.web.servlet.resource. ResourceHttpRequestHandler]
 2015-07-11 14: 24: 54.723 INFO 12590 --- [main] osjeaAnnotationMBeanExporter: Registering beans for JMX exposure on startup
 2015-07-11 14: 24: 54.800 INFO 12590 --- [main] sbcetTomcatEmbeddedServletContainer: Tomcat started on port (s): 8080 (http)
 2015-07-11 14: 24: 54.803 INFO 12590 --- [main] habraspring.Application: Started Application in 5.945 seconds (JVM running for 6.529) 


Try to go now to http: // localhost: 8080 , it should work.

Well, now I would like to see some content, right?

2.3 Content


To do this, we need to create two configuration classes (remember, no XML!) In the config subfolder, and also add home.html (similar to index.html) to the resources folder.

We have the simplest configuration files, because we use them for pages without a controller:

config / MvcConfig.java
 package habraspring.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration public class MvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/home").setViewName("home"); registry.addViewController("/").setViewName("home"); } } 


config / WebSecurityConfig.java
 package habraspring.config; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; @Configuration @EnableWebMvcSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll(); } } 


Well, the simplest home page:

home.html
 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Habrahabr</title> </head> <body> <h1>Welcome!</h1> <p>Yours home page.</p> </body> </html> 



How the project should look like at this stage can be viewed (and downloaded) here:
github.com/MaxPovver/ForHabrahabr/tree/withbasicmvc
* do not forget to write in the project folder in the console git checkout withbasicmvc

If at the moment everything is done correctly, http: // localhost: 8080 should show you
 Welcome!

 Yours home page.


3. Add work from the database


So, we want to add controllers, and that access to them is granted only to authorized users, but we do not have them yet.

In order for the authorization mechanism to work, we need to add the essence of the user to the project.
Necessary steps:

3.1 Entity “User”


First, create in DB the simplest table users with the fields id, username, password.

Now we will create a subpackage of entities for the entities and create the class User in it:

entities / User.java
 package habraspring.entities; import javax.persistence.*; @Entity @Table(name="users") public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String username; private String password; public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } protected User(){} public User(String name, String pass) { username = name; password = pass; } } 



No hbm.xml is needed, you do not even need to annotate the fields (an exception is the ID field, you should always mark it)

3.2 UsersRepository Repository


Here Spring does everything for us, it’s enough to be inherited so that he understands what to generate, you don’t need to write code at all:

UsersRepository.java
 package habraspring.repositories; import habraspring.entities.User; import org.springframework.data.repository.CrudRepository; public interface UsersRepository extends CrudRepository<User, Long> { User findByUsername(String username); } 



3.3 Add a connection between the user and Spring Security


To do this, we need to create a class that implements the UserDetailsService interface and enable it in WebSecurityConfig

utils / MySQLUserDetailsService.java
 package habraspring.utils; import habraspring.entities.User; import habraspring.repositories.UsersRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Collection; import java.util.List; @Service public class MySQLUserDetailsService implements UserDetailsService { @Autowired UsersRepository users; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserDetails loadedUser; try { User client = users.findByUsername(username); loadedUser = new org.springframework.security.core.userdetails.User( client.getUsername(), client.getPassword(), DummyAuthority.getAuth()); } catch (Exception repositoryProblem) { throw new InternalAuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem); } return loadedUser; } static class DummyAuthority implements GrantedAuthority { static Collection<GrantedAuthority> getAuth() { List<GrantedAuthority> res = new ArrayList<>(1); res.add(new DummyAuthority()); return res; } @Override public String getAuthority() { return "USER"; } } } 


Now, change the WebSecurityConfig code:

Spoiler header
 package habraspring.config; import habraspring.utils.MySQLUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; @Configuration @EnableWebMvcSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } @Autowired private MySQLUserDetailsService mySQLUserDetailsService; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(mySQLUserDetailsService); } } 


Add the login.html login page and the "secret" (for authorized only) secret.html page:

Their code
secret.html
 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Secret page</title> </head> <body> <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1> <form th:action="@{/logout}" method="post"> <input type="submit" value="Sign Out"/> </form> </body> </html> 


login.html
 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Login page</title> </head> <body> <div th:if="${param.error}"> Invalid username and password. </div> <div th:if="${param.logout}"> You have been logged out. </div> <form th:action="@{/login}" method="post"> <div><label> User Name : <input type="text" name="username"/> </label></div> <div><label> Password: <input type="password" name="password"/> </label></div> <div><input type="submit" value="Sign In"/></div> </form> </body> </html> 



And we will make the new pages available without a controller, adding 2 lines to WebMvcConfig:

  registry.addViewController("/login").setViewName("login"); registry.addViewController("/secret").setViewName("secret"); 

Done! Now, at http: // localhost: 8080 , everything should be displayed normally,
but at http: // localhost: 8080 / secret you will not be able to get through - they will throw in / login, requiring a valid user / password pair.

Now add to your forhabrahabr.users table an entry with a password and login user, user (or run the script github.com/MaxPovver/ForHabrahabr/blob/withauth/import_me.sql in your db).
If you did everything right, you should now be allowed into / secret.

4. What we have come to


So, we already use a full-fledged Spring MVC application using Spring Security for Security and Spring JPA for working with the database. And no XML.

4.1 For those who want to launch a finished project




Much had to be omitted / not explained in order not to confuse it completely, but if you consider it necessary to add something now - write in ps.

There is still material left for at least one part, if, of course, the topic is relevant. (Controllers, EntityToEntity (ManyToOne OneToOne etc), User Roles, Testing etc)

Comments to the first part


In the article itself I had to skip some moments, I will try to write about their maximum number here. This part is not needed to run the application, but can be useful in clarifying incomprehensible moments.
To read...

Mvcconfig


I will cite his code again:

MvcConfig.java
 package habraspring.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration public class MvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/home").setViewName("home"); registry.addViewController("/").setViewName("home"); registry.addViewController("/login").setViewName("login"); registry.addViewController("/secret").setViewName("secret"); } } 


What does this method do? It binds a request from the address bar to a template from the resources / folder.
For example, if our server is asked to show the contents of "/" or "/ home", it will return home.html.
Similarly, when requesting "/ login", login.html will return.

WebSecurityConfig


WebSecurityConfig.java
 package habraspring.config; import habraspring.utils.MySQLUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; @Configuration @EnableWebMvcSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } @Autowired private MySQLUserDetailsService mySQLUserDetailsService; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(mySQLUserDetailsService); } } 


Consider this class in order:
We allow to give requests from this list to anyone who requested:

 .authorizeRequests() .antMatchers("/", "/home").permitAll() 

All the rest is allowed to open only to authorized users, we specify where the login form is, by opening it for all and the page to exit:

 .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); 

We also determine where to get users from our system of protection, for this we use the @Autowired annotation, Spring will load the instance of the necessary service there:

  @Autowired private MySQLUserDetailsService mySQLUserDetailsService; 

And we transfer it to the method that allows us to define our service for connecting Spring Security users and users from the database.

  @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(mySQLUserDetailsService); } 

MySQLUserDetailsService


MySQLUserDetailsService.java
 package habraspring.utils; import habraspring.entities.User; import habraspring.repositories.UsersRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Collection; import java.util.List; @Service public class MySQLUserDetailsService implements UserDetailsService { @Autowired UsersRepository users; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserDetails loadedUser; try { User client = users.findByUsername(username); loadedUser = new org.springframework.security.core.userdetails.User( client.getUsername(), client.getPassword(), DummyAuthority.getAuth()); } catch (Exception repositoryProblem) { throw new InternalAuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem); } return loadedUser; } static class DummyAuthority implements GrantedAuthority { static Collection<GrantedAuthority> getAuth() { List<GrantedAuthority> res = new ArrayList<>(1); res.add(new DummyAuthority()); return res; } @Override public String getAuthority() { return "USER"; } } } 


Consider the implementation of loadUserByUsername.

Here, we again use @Autowired to spring up in the users' implemented user interface of the repository from the database and pull out with its help the user with the specified nickname from the database:

  @Autowired UsersRepository users; .... User client = users.findByUsername(username); 

And here we return the “converted” from the database entity to the Spring Security entity user. But there is a problem - our user does not have associated roles yet (we’ll make them later), so we will create a stub class that gives user rights to any existing user. If the user does not exist, the code will crash earlier with the exception. Then Spring itself for this user will check the correspondence of the entered password and the password of the object in the database with the following username:

  loadedUser = new org.springframework.security.core.userdetails.User( client.getUsername(), client.getPassword(), DummyAuthority.getAuth()); 

User


User.java
 package habraspring.entities; import javax.persistence.*; @Entity @Table(name="users") public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String username; private String password; public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } protected User(){} public User(String name, String pass) { username = name; password = pass; } } 


Consider the code line by line:
By adding the Entity annotation, we indicate to the Spring scanner that this class should be tied to a table in the database.
In the Table annotation, we indicate the name of the table to which we will bind this class (often it can also not be indicated, but it is better not to do so, otherwise, if you change the name of the table, you can catch problems).

 @Entity @Table(name="users") public class User { 

With fields, everything is much simpler - they are automatically attached to fields in the database with the same name, you don’t need to write anything at all! You only need to specify which field - ID, and how to generate it for new entities while saving to the base.

  @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String username; private String password; 

Next are automatically generated getters / setters (it is better to do them even if they are not needed, just in case SUDDENLY want to add logic to it).

Well, after there are two constructors - empty - only for the serializer, you cannot use it directly in the program, and user friendly to create new users and then save them in the UsersRepository.

  protected User(){} public User(String name, String pass) { username = name; password = pass; } 

Gradle


 buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.5.RELEASE") classpath 'mysql:mysql-connector-java:5.1.34' } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'idea' apply plugin: 'spring-boot' jar { baseName = 'gs-rest-service' version = '0.1.0' } repositories { mavenCentral() } sourceCompatibility = 1.8 targetCompatibility = 1.8 dependencies { compile("org.springframework.boot:spring-boot-starter-web") compile("org.springframework.boot:spring-boot-starter-data-jpa") compile("org.springframework.boot:spring-boot-starter-security") compile("org.springframework.boot:spring-boot-starter-thymeleaf") compile 'mysql:mysql-connector-java:5.1.31' compile 'commons-dbcp:commons-dbcp:1.4' testCompile("org.springframework:spring-test") testCompile("junit:junit") testCompile 'org.springframework.security:spring-security-test:4.0.1.RELEASE' } task wrapper(type: Wrapper) { gradleVersion = '2.3' } 

, Gradle :

 dependencies { compile("org.springframework.boot:spring-boot-starter-web") -   Spring MVC compile("org.springframework.boot:spring-boot-starter-data-jpa") -   Spring Jpa(   ) compile("org.springframework.boot:spring-boot-starter-security") -   Spring Security compile("org.springframework.boot:spring-boot-starter-thymeleaf") -     resources/tempates compile 'mysql:mysql-connector-java:5.1.31' - mysql to spring compile 'commons-dbcp:commons-dbcp:1.4' 


, ( ):

  testCompile("org.springframework:spring-test") testCompile("junit:junit") testCompile 'org.springframework.security:spring-security-test:4.0.1.RELEASE' 



UPD second part: Spring without XML. Part 2

Source: https://habr.com/ru/post/262323/


All Articles