> For the complete documentation index, see [llms.txt](https://opu.gitbook.io/oop/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://opu.gitbook.io/oop/2019-2020-archive/arkhiv-1/lectures-1/lecture-7.md).

# 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;
    }
}
```
