# 7. Работа со Spring Security, часть 1

**Spring Security** – это фреймворк обеспечения безопасности, предоставляющий возможность декларативного управления безопасностью приложений на основе фреймворка Spring.

Создадим новый проект, который включает модуль Spring Security или добавим в существующий проект зависимость

{% code title="pom.xml" %}

```markup
<dependencies>
    ...
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
    ...
    
</dependencies>
```

{% endcode %}

При попытке перейти на любой URL-адрес проекта нас перенаправит на форму ввода логина и пароля

![](/files/-M3wnJlZOgHN5EYLWuSG)

По умолчанию, логином является **user**, а пароль генерируется каждый раз при старте приложения.

![](/files/-M3wn_N2NnGRF9_QiZgu)

Если вы ввели правильно логин и пароль, то сервер переадресует вас на указанный URL.

![](/files/-M3wnpb8kIXmvUrlnhQa)

В файле **application.properties** вы можете указать желаемый логин и пароль для пользователя по умолчанию (в данной лекции будет использоваться конфигурация с помощью языка yaml).

{% code title="resources/application.yml" %}

```yaml
spring:
  security:
    user:
      name: nick
      password: 1234
```

{% endcode %}

Фреймворк Spring Security "из коробки" предоставляет вам возможность простой версии так называемой **form-based** аутентификации. Если быть точнее, то по умолчанию, Spring Security реализует следующее поведение:

* добавляет обязательный процесс аутентификации для всех URL;
* добавляет форму для входа;
* обрабатывает ошибки формы ввода;
* создает пользователя по умолчанию и генерирует пароль.

### &#x20;Основные понятия, связанные со Spring Security:

**Authentication**

**Authorization**

**Principal** - текущий залогиненный пользователь или текущий залогиненный аккаунт (если у одного физического лица или программы есть несколько аккаунтов, то тогда ему будет соответствовать несколько возможных principal\`ов). Иногда, в общем случае, **principal** - это субъект, который принимает участие в осуществлении процедур безопасности. В качестве **principal** могут выступать люди, компьютеры, службы, процессы или их группа;

**Granted Authority** - ;

**Role** - .

## Настройка процесса аутентификации в Spring

Для того, чтобы сконфигурировать процесс аутентификации, необходимо создать объект **AuthenticationManager**, в котором следует указать требуемые параметры аутентификации. Объект типа **AuthenticationManager** обычно настраивают с помощью builder\`а, который имеет тип **AuthenticationManagerBuilder**.

Добавим класс SecurityConfig, который наследуется от класса WebSecurityConfigurerAdaper. Также укажем аннотации @Configuration (это означает, что данный класс является конфигурационным) и @EnableWebSecurity (это означает, что данный класс является содержит настройки для защиты веб-приложения).

```java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
}
```

Переопределим метод configure(), который принимает на вход объект типа **AuthenticationManagerBuilder** (обратите внимание, что нам нужна именно эта версия перегруженного метода).

```
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
    }
}
```

Для начала укажем, что источник аутентификации это жестко прописанные пользователи (так называемая **inMemoryAuthentication()**. Далее указываем логин, пароль и роль пользователя.

```
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("foo")
                .password("bar")
                .roles("USER");
    }
}
```

Если необходимо указать несколько пользователей, после параметров первого пользователя вызываете метод **and()** после чего указываете параметры следующего пользователя.

```
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("foo")
                .password("bar")
                .roles("USER")
                .and()
                .withUser("nick")
                .roles("MODERATOR");
    }
}
```

#### Хеширование паролей

Хранить пароли без хеширования является грубейшим нарушением правил безопасности, поэтому нам необходимо добавить процесс хеширования пароля в нашу систему.&#x20;

Не будем вдаваться в подробности различных алгоритмов хеширования пароля, просто скажем, что на даннай момент рекомендуемым является алгоритм **Bcrypt**. Для обеспечения хеширования, вы можете поступить несколькими способами.

Первый способ - создайте Bean, который будет возвращать объект Encoder\`а и добавьте его как метод конфигурационного класса.

![](/files/-M440jpePOeiQX8mELHb)

Далее найдите в интернете генератор хеша с помощью алгоритма Bcrypt, скопируйте хеш для вашего пароля в метод password.

```
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("foo")
                .password("$2y$12$kElDOfhm4WgdsDc4UQjgtuz0VEi5MOqVVhXaMoD1F2lhLivokhCqe")
                .roles("USER")
                .and()
                .withUser("nick")
                .roles("MODERATOR");
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
```

Если не хотите использовать Bean для хеширования пароля, можете в начале хеша добавить обозначение, что это хеш для алгоритма bcrypt.

```
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("foo")
                .password("{bcrypt}$2y$12$.JzV1A3qlof1.NzZpGaTYO1b26JGHevg0900QvwrSOdU3U9.g4hta")
                .roles("USER");
    }

//    @Bean
//    public PasswordEncoder getPasswordEncoder() {
//        return new BCryptPasswordEncoder();
//    }
}
```

### Настройка процесса авторизации

**Добавим класс контроллера**

```
@Controller
public class IndexController {

    @GetMapping("/")
    public String index() {
        return "index";
    }

    @GetMapping("/user/index")
    public String user_index() {
        return "/user/index";
    }

    @GetMapping("/admin/index")
    public String admin_index() {
        return "/admin/index";
    }
}
```

**Изменим SecurityConfig**

```
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("foo")
                .password("$2y$12$kElDOfhm4WgdsDc4UQjgtuz0VEi5MOqVVhXaMoD1F2lhLivokhCqe")
                .roles("USER")
                .and()
                .withUser("admin")
                .password("$2y$12$DQlTV6V1wMKEoCIW5lo1huAn2/bRk4hULDmRS5Jw6YW7HayHV4K66")
                .roles("ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "static/css", "static/js").permitAll()
                .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
                .antMatchers("/admin/**").hasRole("ADMIN")
                .and().formLogin();
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
```

**Изменим formLogin() на httpBasic().**

**Создадим MyUserDetailsService**

```
@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    UserRepository repository;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        return new MyUserDetails(s);
    }
}
```

**Создадим MyUserDetails**

```
public class MyUserDetails implements UserDetails {
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
    }

    private String userName;

    public MyUserDetails(String userName) {
        this.userName = userName;
    }

    public MyUserDetails() {
    }

    @Override
    public String getPassword() {
        return "$2y$12$8fKhxW71f4DzkzXYCh592.I.cd1uKkMrNwrHAApR1x5KHJ3qy1IjS";
    }

    @Override
    public String getUsername() {
        return userName;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}
```

Добавим в pom.xml

```
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- PostgreSQL -->
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>
```

Настроим подключение к БД

{% code title="application.yml" %}

```yaml
  jpa:
    database: POSTGRESQL
    show-sql: true
    hibernate:
      ddl-auto: create-drop
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
  datasource:
    platform: postgres
    url: jdbc:postgresql://localhost:5432/ejournal
    username: ejournal_user
    password: 123456
    driverClassName: org.postgresql.Driver
```

{% endcode %}

**Добавим сущность User**

```
@Entity
@Table(name = "profile")
@Data
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @Column(name = "user_name")
    private String userName;
    private String password;
    private boolean active;
    private String roles;
}
```

**Изменим MyUserDetailsService**

```
@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    UserRepository repository;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        Optional<User> user = repository.findByUserName(s);

        user.orElseThrow(() -> new UsernameNotFoundException("User not found: " + s));

        return user.map(MyUserDetails::new).get();
    }
}
```

**Создадим UserRepository**

```
public interface UserRepository extends JpaRepository<User, Integer> {

    Optional<User> findByUserName(String userName);
}
```

**Изменим MyUserDetails**

```
public class MyUserDetails implements UserDetails {
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorityList;
    }

    private String userName;
    private String password;
    private boolean active;
    private List<GrantedAuthority> authorityList;

    public MyUserDetails(User userName) {
        this.userName = userName.getUserName();
        this.password = userName.getPassword();
        this.active = userName.isActive();
        this.authorityList = Arrays.stream(userName.getRoles().split(",")).map(SimpleGrantedAuthority::new).collect(Collectors.toList());
    }

    public MyUserDetails() {
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return userName;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return active;
    }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://opu.gitbook.io/oop/2019-2020-archive/arkhiv-1/lectures-1/lecture-7.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
