Тема безопасности в современных реалиях крайне важна. Защите персональных данных уделяют много средств все передовые IT компании. В этой статье мы рассмотрим возможность ограничения доступа к веб приложению по средствам Spring Security.
Подготовка
Первым делом заходим на spring starter и создаем новый проект:
Импортим проект в IDE и видим следующий билд и сносим нафиг билд, так как уже есть прецеденты один и два.
Рабочий билд:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
buildscript { ext { springBootVersion = '1.2.5.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") classpath("io.spring.gradle:dependency-management-plugin:0.5.2.RELEASE") } } apply plugin: 'groovy' apply plugin: 'eclipse' apply plugin: 'idea' apply plugin: 'spring-boot' apply plugin: 'io.spring.dependency-management' jar { baseName = 'security' version = '0.0.1-SNAPSHOT' } sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile("org.vaadin.spring:spring-boot-vaadin:0.0.5.RELEASE"){ exclude(group: "javax.validation") } compile("com.vaadin:vaadin-client-compiled:7.6.0.alpha2"){ exclude(group: "com.vaadin", module: "vaadin-client") } compile("com.vaadin:vaadin-themes:7.6.0.alpha2") compile("com.vaadin:vaadin-client:7.6.0.alpha2"){ exclude(group: "javax.validation") } compile 'org.codehaus.groovy:groovy' compile 'org.hibernate:hibernate-entitymanager:4.3.10.Final' compile 'org.springframework.boot:spring-boot-starter-data-jpa:1.2.5.RELEASE' compile 'mysql:mysql-connector-java:5.1.1' compile 'org.springframework.boot:spring-boot-starter-security:1.2.5.RELEASE' testCompile 'org.springframework.boot:spring-boot-starter-test' } dependencyManagement { imports { mavenBom "com.vaadin:vaadin-bom:7.6.0.alpha2" } } eclipse { classpath { containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER') containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8' } } task wrapper(type: Wrapper) { gradleVersion = '2.3' } |
Задача
Сделаем простенькое веб приложение, в котором необходимо предусмотреть уровни доступа. Пусть будет три уровня доступа:
- public – этот уровень будет доступен любым не авторизированным и авторизированным пользователям.
- protected – этот уровень будет доступен авторизированным пользователям.
- private – этот уровень доступа только для админа.
Список пользователей будет находится в БД, соответственно авторизацию нужно сверять с базой.
Так же посмотрим как построить взаимодействие Spring Security с Vaadin UI.
Поехали!
Для того, чтобы сообщить Spring какие ссылки необходимо ограничить в доступе, а какие оставить в публичном доступе, а так же распределить по ролям, нужно унаследоваться от класса
1 |
WebSecurityConfigurerAdapter |
в котором перекрыть метод
1 2 |
@Override protected void configure(HttpSecurity http) |
HttpSecurity представляет собой класс позволяющий настроить безопасность http запросов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@Override protected void configure(HttpSecurity http) throws Exception { http. authorizeRequests() .antMatchers('/','/log1n',"/VAADIN/**", "/PUSH/**", "/UIDL/**").permitAll() .antMatchers('/user/**').access("hasRole('ROLE_USER')") .antMatchers('/admin/**').access("hasRole('ROLE_ADMIN')") .anyRequest().authenticated() .and() .formLogin() .usernameParameter('username') .passwordParameter('password') .and() .logout() .permitAll() .and() .csrf().disable() .exceptionHandling() .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/log1n")) } |
Разберем настройку подробнее:
1 |
antMatchers('/','/log1n',"/VAADIN/**", "/PUSH/**", "/UIDL/**").permitAll() |
antMathcers – на вход принимает список ссылок, к которым будет применено правило.
permitAll – правило, которое сообщает о том, что ссылки доступны любому пользователю.
hasRole – правило, которое требует наличие определенной роли от пользователя.
access – правило, которое обеспечивает защиту описанным в строковом виде атрибутом, пример из документации:
1 |
hasRole('ROLE_USER') and hasRole('ROLE_SUPER') |
т.е. если пользователь должен обладать множеством ролей, используется access.
authenticationEntryPoint – еще одна интересная настройка, которая определяет ссылку аутентификации, если пользователь не залогинился и пытается проникнуть в запретную зону, то Spring Security его отошлет на эту точку входа.
!Важно! на данный момент vaadin и spring boot security в рамках используемых в статье зависимостей имеют конфликт. Если задать Vaadin Login UI маппинг по ссылке ‘/login’ и в Spring Security определить entryPoint на туже ссылку, мы столкнемся с зависшим приложением.
Поэтому используем path log1n
anyRequest – все оставшиеся URL, которые еще не были промаппированны.
autheticated - правило требующее авторизации.
Настройка этим не ограничивается. Она очень гибкая и в рамках этой статьи достаточно приведенного выше кода.
Проверим как это работает.
Переходим по ссылке
1 |
http://localhost:8080/ |
Видим доступную в публичном доступе страницу. Попробуем перейти на
1 |
http://localhost:8080/user/ |
И как ожидалось нас не пустило и перенаправило на страницу авторизации. Но пока заходить некому, так как мы не сообщили Spring Security об пользователях. Добавим пару пользователей.
Для этого необходимо сконфигурировать класс:
1 |
AuthenticationManagerBuilder |
Для начала просто добавим в “память” Spring Security двух пользователей user и admin
1 2 3 4 5 6 7 8 |
@Autowired void configureGlobal(AuthenticationManagerBuilder auth){ auth .inMemoryAuthentication() .withUser("user").password("password").roles("USER") .and() .withUser("admin").password("admin").roles("ADMIN") } |
После того как зарегестрировали двух пользователей, необходимо авторизироваться. Воспользуемся для этого менеджером подключения:
1 2 |
@Autowired AuthenticationManager manager |
В который нужно передать токен подключения:
1 |
UsernamePasswordAuthenticationToken |
И передать подключение в контекст Spring Security
1 2 |
def authentication = new UsernamePasswordAuthenticationToken('user', 'password') SecurityContextHolder.context.authentication = manager.authenticate(authentication) |
Успешно пускает по запросу user.
Если попробуем перейти по
1 |
http://localhost:8080/admin/ |
то получим
1 |
org.springframework.security.access.AccessDeniedException: Access is denied |
В следующей части рассмотрим регистрацию пользователей с помощью БД.