Страницы

Поиск по вопросам

Показаны сообщения с ярлыком spring-security. Показать все сообщения
Показаны сообщения с ярлыком spring-security. Показать все сообщения

четверг, 2 апреля 2020 г.

Spring Security: как сделать разделение ролей?

#java #spring #spring_mvc #spring_security

                    
Как в Spring сделать разделение ролей? Если, например, стоит, что администратор (значение
из базы) true, то открыта конкретная ссылка для просмотра в jsp и на эту страницу (на
которую вела ссылка) вход закрыт прочим пользователям (с другими ролями).

Это делается через Spring Security, верно?

UPD: Буду рад любым советам. Кто-то поставил минус, просьба отписаться в комментариях.
    


Ответы

Ответ 1



Как-то так, например some_page.jsp Админка SomeController.java @Controller public class SomeController { @RequestMapping(path = "/admin", method = RequestMethod.GET) @PreAuthorize("hasRole('ROLE_ADMIN')") public String admin(Model model) { return "admin"; } } Совет простой - попробуйте почитать документацию, там всё очень подробно описано.

пятница, 10 января 2020 г.

Как мне не искать каждый раз пользователя в Spring?

#java #spring_mvc #spring_security


В общем при каждом обращении к котроллер в Spring мне приходится искать пользователя,
создавать объект и уже работать с ним. Т.е. скажем захожу в /profile, я вытаскиваю
имя авторизованного пользователя, ищу в Базе его, если всё нормально то работаю с ним.
Кликаю по /index, то опять приходиться искать пользователя в базе, создаю объект и
опять выстраиваю страницу под него. Как избежать поиск и создание нового объекта?
    


Ответы

Ответ 1



Можете попробовать кешировать пользователей, но это отразится на ресурсах. Можете так же хранить их в сессии, но это понизит безопасность приложения. А на самом деле это нормально. И я бы на вашем месте начал задумываться над этим вопросом если бы только это действительно сильно сказывалось на скорости работы вашего приложения. Кажется это называется преждевременной оптимизацией. К слову, подумал что вы, возможно, хотите сбежать не от постоянных запросов в базу, а от регулярного повторяющегося кода? в таком случае уже можно подумать как это решить со стороны организации самого кода.

Ответ 2



Так же не плохим вариантом будет работа с cookies, для подтверждения личности пользователя)

среда, 1 января 2020 г.

Возможность авторизации запросов в spring и access_token-ом и через форму входа в браузере

#java #spring #kotlin #spring_boot #spring_security


Дано:

Проект на spring+kotlin. Реализовано получение access_token через БД и доступ к ресурсам
с его помощью.

Задача:

Реализовать возможность доступа к ресурсам для юзеров, залогинившихся через форму
логина в браузере.

Проблема:

Работает только один из способов авторизации. Если добавить аннотацию @EnableResourceServer
- работает способ доступа с токеном. При запросе любой страницы в браузере просто выводит
ошибку "неавторизован", в т.ч. по адресу /login. Если на /login зайти с прикреплением
токена - выведет 404. Если аннотацию @EnableResourceServer убрать - работает доступ
через форму логина в браузере и получение токена, но все запросы с прикреплением токена
перенаправляют на /login, т.е. токен не воспринимается.

Вопрос:

Как же сделать так, чтобы работало?

По идее надо написать что-то тут, но часы гугленья не дали понимания что именно:

@Configuration
@EnableWebSecurity
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
class WebSecurityConfiguration : WebSecurityConfigurerAdapter() {

    @Bean
    fun passwordEncoder() = BCryptPasswordEncoder()

    @Bean
    fun authenticationProvider(): DaoAuthenticationProvider {
        val authenticationProvider = DaoAuthenticationProvider()
        authenticationProvider.setUserDetailsService(userDetailsService)
        authenticationProvider.setPasswordEncoder(passwordEncoder())
        return authenticationProvider
    }

    @Bean
    override fun authenticationManagerBean(): AuthenticationManager {
        return super.authenticationManagerBean()
    }

    @Autowired
    lateinit var userDetailsService: UserServiceImpl

    @Autowired
    fun configureGlobal(auth: AuthenticationManagerBuilder) {
        auth
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder())
                .and()
                .authenticationProvider(authenticationProvider())
    }

    override fun configure(http: HttpSecurity) {
        http
                .csrf().disable()
        http
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin().permitAll()
    }
}


Дополнительная информация:

Ссылка на весь проект на GitHub: https://github.com/mohaxspb/springSecurityExample/tree/v0.0.1 

Для запуска и работы надо поставить postgresql и создать там под юзером postgres
с паролем testtest БД с именем springbootdb - далее при запуске в БД создана будет
таблица с юзерами и туда будет добавлен юзер, под коим можно логиниться с логином test@test.ru
и паролем password. Для получения токена надо указать client_id: client_id и client_secret:
client_secret (эти данные также пишутся в БД при старте сервера)
    


Ответы

Ответ 1



Проблема решена. Заключалась она в том, что при добавлении аннотации @EnableResourceServer в цепочку фильтров для каждого запроса добавлялся OAuth2AuthenticationProcessingFilter, в настройках которого по умолчанию включён явный запрет на аутентификацию с помощью Cookie, которые используются при входе через форму логина. С другой стороны, если убрать аннотацию @EnableResourceServer, то фильтр OAuth2AuthenticationProcessingFilter не будет добавлен и спринг не будет знать, что на запросы с токеном надо реагировать попыткой аутентификации по токену. В итоге я сделал так: Убрал аннотацию @EnableResourceServer. Вручную добавил OAuth2AuthenticationProcessingFilter в цепочку фильтров для каждого запроса указав ему, что можно пропускать запросы с аутентификацией с помощью Cookie Этому фильтру установил AuthenticationManager типа OAuth2AuthenticationManager OAuth2AuthenticationManager - это дополнительный AuthenticationManager, которому передана реализация ClientDetailsService (отвечает за предоставление данных о клиентском приложении для авторизации) и реализация ResourceServerTokenServices, которая превращает access_token в объект Authentication с типом OAuth2Authentication Т.к. спрингу надо знать какой AuthenticationManager главный, надо дефолтный пометить аннотацией @Primary Таким образом, когда спринг не находит в запросе куки или токен он перенаправляет на страницу логина. После ввода данных устанавливает в браузере куку и аутентифицирует запросы ею. Если же в запросе есть токен, то срабатывает добавленный фильтр и пытается по токену аутентифицировать запрос. В случае удачи устанавливает аутентификацию в SecurityContext. В итоге реализация WebSecurityConfigurerAdapter выглядит так: @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) class WebSecurityConfiguration : WebSecurityConfigurerAdapter() { @Autowired lateinit var clientDetailsService: ClientServiceImpl @Bean fun tokenServices() = AccessTokenServices() @Bean fun passwordEncoder() = BCryptPasswordEncoder() @Bean fun authenticationProvider(): DaoAuthenticationProvider { val authenticationProvider = DaoAuthenticationProvider() authenticationProvider.setUserDetailsService(userDetailsService) authenticationProvider.setPasswordEncoder(passwordEncoder()) return authenticationProvider } @Primary @Bean override fun authenticationManagerBean(): AuthenticationManager { return super.authenticationManagerBean() } @Autowired lateinit var userDetailsService: UserServiceImpl @Autowired fun configureGlobal(auth: AuthenticationManagerBuilder) { auth .authenticationProvider(authenticationProvider()) .userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder()) } @Bean fun oauth2authenticationManager(): OAuth2AuthenticationManager { val authManager = OAuth2AuthenticationManager() authManager.setClientDetailsService(clientDetailsService) authManager.setTokenServices(tokenServices()) return authManager } @Bean fun myOAuth2Filter(): Filter { val filter = OAuth2AuthenticationProcessingFilter() filter.setAuthenticationManager(oauth2authenticationManager()) //allow auth with cookies (not only with token) filter.setStateless(false) return filter } override fun configure(http: HttpSecurity) { http .csrf() .disable() http .authorizeRequests() .anyRequest() .authenticated() http .formLogin() .permitAll() http .addFilterBefore( myOAuth2Filter(), BasicAuthenticationFilter::class.java ) } } Весь код в репозитории под тегом v0.03 Наверное, можно сократить код, корректно реализовав ResourceServerConfigurerAdapter и вернув @EnableResourceServer. Если получится - дополню ответ.

пятница, 27 декабря 2019 г.

Spring Security OAuth2

#java #spring #spring_security #oauth2


Есть сервис с авторизацией через OAuth2 (например, vk.com). Есть clientId и clientSecret.
Нужно написать демона, который будет делать какие-то запросы к API сервиса.

Возможно ли это сделать в фоновом режиме и автоматически получать access_token по
истечению срока? Например, многие сервисы после авторизации больше не запрашивают данные
пользователя.

UPD. Мне нужно сохранять access_token (например, в БД), а потом из обычного потока
работать с OAuth2RestTemplate.

public void run() {
    // access_token = получаем из БД сохраненный access_token.
    // создаем restTemplate из access_token
    while (true) {
        Map map = restTemplate.getForObject(..., Map.class);
    }
}

    


Ответы

Ответ 1



После долгих попыток рецепт получился следующим. Получаем access_token из базы данных в новом потоке; Оборачиваем его в DefaultOAuth2AccessToken; Создаем DefaultOAuth2ClientContext и добавляем в него DefaultOAuth2AccessToken; Создаем OAuth2RestTemplate с настройками и установленным контекстом. Выполняем запросы и ловим UserRedirectRequiredException. Исключение означает, что ключ больше не валидный и нужно запросить новый у пользователя через контроллер.

Spring security и бан IP

#java #spring #spring_security


Есть ли встроенная возможность в Spring Security банить пользователя по IP после
определённого количества раз ввода пароля неверно? Или решение только через реализацию
своего провайдера? 
    


Ответы

Ответ 1



Один из вариантов - можно использовать Spring Security's Web Security Expressions. Например: ... А так же можно написать свой CustomAuthProvider, и в нём получить IP адрес пользователя, вот так: wad = (WebAuthenticationDetails) authentication.getDetails(); userIPAddress = wad.getRemoteAddress(); А дальше всё в наших руках :)

вторник, 24 декабря 2019 г.

Ошибка spring security на wildfly

#java #spring #spring_security #spring_boot #wildfly


При логине в приложение на spring-security выдает следующее сообщение:    

{"timestamp":1464679377206,"status":999,"error":"None","message":"No message available"}


и редиректит на error-page в приложении развернутом на wildfly, при запуске spring-boot
такой проблемы нет. При чем, если затем вбить правильный адрес руками все работает
корректно.
Класс запуска приложения:

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(applicationClass);
    }

    private static Class applicationClass = Application.class;

}


Thymeleaf форма логина

        


Ответы

Ответ 1



Ошибка появлялась из-за того, что папка с картинками не была включена в проект, но была прописана в spring-security конфигурации. Соответственно, эту папку спринг не находил и ругался.

пятница, 13 декабря 2019 г.

Spring-Security Как получить авторизованного пользователя

#java #spring #hibernate #spring_security #j2ee


Всем привет! Как можно получить авторизованного пользователя, т.е после авторизации
извлечь обьект данного юзера из БД к примеру для того чтобы заполнить поля на его странице
профиля, DAO у меня реализован с помощью hibernate и сама авторизация выполняется из
БД. Заранее спасибо!
    


Ответы

Ответ 1



Для этого сначала нужно получить имя юзера из spring-security public String getCurrentUsername() { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); return auth.getName(); } и передать его в hibernate public User getUserByUsername(String username) { CriteriaQuery criteriaQuery = em.getCriteriaBuilder().createQuery(User.class); Root userRequest = criteriaQuery.from(User.class); Expression exp = userRequest.get("username"); Predicate predicate = exp.in(username); criteriaQuery.where(predicate); try { return em.createQuery(criteriaQuery).getSingleResult(); } catch (NoResultException e) { return new User(); } }

Ответ 2



Начиная с Spring-Security 3.2 удобнее воспользоваться аннотацией @AuthenticationPrincipal на аргументе метода вашего контроллера. ModelAndView someRequestHandler(@AuthenticationPrincipal User user) { // ... } User - это именно спринговый юзер, а не тот объект, который вы кладете в БД. Чтобы это работало, нужно зарегистрировать бин AuthenticationPrincipalArgumentResolver: при использовании Java-конфигурации - повесть аннотацию @EnableWebMvcSecurity на ваш @Configuration-бин; при использовании xml-конфигурации - зарегистрировать его в :

среда, 17 июля 2019 г.

Spring Security: как поменять HTTP Status для response?

При неудачной авторизации мне необходимо отдавать обратно статус 401. Поскольку у нас в проекте используется Spring Security, то и настройка авторизации настраивается через него:
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatcher(ADMIN_MATCHERS) .authorizeRequests() .antMatchers(ADMIN_MATCHERS) .access("hasRole('ADMIN')") .anyRequest().authenticated() .and() .formLogin() .usernameParameter(USERNAME) .passwordParameter(PASSWORD) .loginPage(ADMIN_LOGIN) .permitAll() .loginProcessingUrl("/admin/login/auth") .failureHandler(customAuthFailureHandler) .successHandler(successHandler()) .and() .logout() .logoutUrl("/admin/logout") .logoutSuccessUrl(ADMIN_LOGIN) .and() .exceptionHandling() .accessDeniedPage(ADMIN_LOGIN) .and() .csrf().disable() .httpBasic();
Для обработки ошибки авторизации я, как мы видим, использовал failureHandler()
И написал кастомный обработчик:
@Component("customAuthFailureHandler") public class CustomAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler { private static final String ADMIN_LOGIN = "/admin/login"; private static final Integer STATUS_UNAUTHORIZED = 401; private static final String RESPONSE_CODE_KEY = "Response-Code"; private static final String RESPONSE_BAD_CREDENTIALS = "bad-credentials";
@Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.setStatus(STATUS_UNAUTHORIZED); response.addHeader(RESPONSE_CODE_KEY, RESPONSE_BAD_CREDENTIALS); getRedirectStrategy().sendRedirect(request, response, ADMIN_LOGIN); } }
Header пересылается без проблем, но вместо статуса 401 ответом приходит 302. То есть, определённый мной статус где-то затирается. Возможно, не нужно использовать sendRedirect?
Подскажите, пожалуйста, рабочее решение в рамках имеющегося кода.


Ответ

В общем, выяснилось, что можно сделать или переадресацию с, допустим, хэдерами, но без HttpStatus, или добавить HttpStatus, но без переадресации. Так, чтоб был и статус, и переадресация, силами Spring Security сделать нельзя.
Я решил задачу следующим образом: создал другую jsp и поменял в настройках строчку
.failureHandler(customAuthFailureHandler)
на
.failureUrl(ADMIN_LOGIN_FAILED)
То же самое можно сделать и силами хэндлера.

вторник, 16 июля 2019 г.

Ошибка при создании бина

Выходит ошибка при компиляции ругается на то что не может создать бин ContactDaoImpl потому что в классе UserRole не правильно использован @OneToMany, а это два разных не связанных класса.
Собственно вот эти классы
UserRole
class UserRole {
@Entity @Table(name = "user_roles", uniqueConstraints = @UniqueConstraint( columnNames = { "role", "username" })) public class UserRole{
private Integer userRoleId; private User user; private String role;
public UserRole() { }
public UserRole(User user, String role) { this.user = user; this.role = role; }
@Id @GeneratedValue(strategy = IDENTITY) @Column(name = "user_role_id", unique = true, nullable = false) public Integer getUserRoleId() { return this.userRoleId; }
public void setUserRoleId(Integer userRoleId) { this.userRoleId = userRoleId; }
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "username", nullable = false) public User getUser() { return this.user; }
public void setUser(User user) { this.user = user; }
@Column(name = "role", nullable = false, length = 45) public String getRole() { return this.role; }
public void setRole(String role) { this.role = role; }
}
ContactDaoImpl
class ContactDaoImpl {
@Repository public class ContactDAOImpl implements ContactDAO {
@Autowired private SessionFactory sessionFactory;
public void addContact(Contact contact) { sessionFactory.getCurrentSession().save(contact); }
@SuppressWarnings("unchecked") public List listContact() { return sessionFactory.getCurrentSession().createQuery("from Contact").list(); }
public void removeContact(Integer id) { Contact contact = (Contact) sessionFactory.getCurrentSession().load( Contact.class, id); if (null != contact) { sessionFactory.getCurrentSession().delete(contact); }
} }
Стектрэйс:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'contactDAOImpl': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: public org.hibernate.SessionFactory kz.tanikin.springtest.dao.ContactDAOImpl.sessionFactory; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in ServletContext resource [/WEB-INF/spring/data.xml]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Use of @OneToMany or @ManyToMany targeting an unmapped class: springtest.domain.User.userRole[springtest.domain.UserRole]
В чем может быть дело? Могу предоставить дополнительную информацию
data.xml


и выходит ошибка
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.filterChains': Cannot resolve reference to bean 'org.springframework.security.web.DefaultSecurityFilterChain#0' while setting bean property 'sourceList' with key [0]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.DefaultSecurityFilterChain#0': Cannot create inner bean '(inner bean)#7bd95c47' of type [org.springframework.security.web.authentication.logout.LogoutFilter] while setting constructor argument with key [3]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#7bd95c47': Cannot resolve reference to bean 'org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices#0' while setting constructor argument with key [1]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices#0': Cannot create inner bean '(inner bean)#232efad5' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#232efad5': Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.core.userdetails.UserDetailsService]: Factory method 'cachingUserDetailsService' threw exception; nested exception is org.springframework.context.ApplicationContextException: More than one UserDetailsService registered. Please use a specific Id reference in or elements.


Ответ

Spring жалуется на то, что не может создать бин contactDAOImpl. Он не может заинжектить в него sessionFactory. А это у него не получается, потому что он не может создать бин sessionFactory. А это, в свою очередь, происходит из-за некорректного использования аннотации @ManyToOne в классе UserRole. Скорее всего, вы не перечислили класс User в конфиге Hibernate. Или, как вариант, вы пометили класс User аннотацией org.hibernate.annotations.Entity вместо javax.persistence.Entity

понедельник, 18 марта 2019 г.

Spring Security OAuth2

Есть сервис с авторизацией через OAuth2 (например, vk.com). Есть clientId и clientSecret. Нужно написать демона, который будет делать какие-то запросы к API сервиса.
Возможно ли это сделать в фоновом режиме и автоматически получать access_token по истечению срока? Например, многие сервисы после авторизации больше не запрашивают данные пользователя.
UPD. Мне нужно сохранять access_token (например, в БД), а потом из обычного потока работать с OAuth2RestTemplate
public void run() { // access_token = получаем из БД сохраненный access_token. // создаем restTemplate из access_token while (true) { Map map = restTemplate.getForObject(..., Map.class); } }


Ответ

После долгих попыток рецепт получился следующим.
Получаем access_token из базы данных в новом потоке; Оборачиваем его в DefaultOAuth2AccessToken Создаем DefaultOAuth2ClientContext и добавляем в него DefaultOAuth2AccessToken Создаем OAuth2RestTemplate с настройками и установленным контекстом. Выполняем запросы и ловим UserRedirectRequiredException. Исключение означает, что ключ больше не валидный и нужно запросить новый у пользователя через контроллер.

понедельник, 25 февраля 2019 г.

Как мне не искать каждый раз пользователя в Spring?

В общем при каждом обращении к котроллер в Spring мне приходится искать пользователя, создавать объект и уже работать с ним. Т.е. скажем захожу в /profile, я вытаскиваю имя авторизованного пользователя, ищу в Базе его, если всё нормально то работаю с ним. Кликаю по /index, то опять приходиться искать пользователя в базе, создаю объект и опять выстраиваю страницу под него. Как избежать поиск и создание нового объекта?


Ответ

Можете попробовать кешировать пользователей, но это отразится на ресурсах. Можете так же хранить их в сессии, но это понизит безопасность приложения.
А на самом деле это нормально. И я бы на вашем месте начал задумываться над этим вопросом если бы только это действительно сильно сказывалось на скорости работы вашего приложения. Кажется это называется преждевременной оптимизацией.
К слову, подумал что вы, возможно, хотите сбежать не от постоянных запросов в базу, а от регулярного повторяющегося кода? в таком случае уже можно подумать как это решить со стороны организации самого кода.

среда, 5 декабря 2018 г.

Spring security и бан IP

Есть ли встроенная возможность в Spring Security банить пользователя по IP после определённого количества раз ввода пароля неверно? Или решение только через реализацию своего провайдера?


Ответ

Один из вариантов - можно использовать Spring Security's Web Security Expressions. Например:
...
А так же можно написать свой CustomAuthProvider, и в нём получить IP адрес пользователя, вот так:
wad = (WebAuthenticationDetails) authentication.getDetails(); userIPAddress = wad.getRemoteAddress();
А дальше всё в наших руках :)

четверг, 29 ноября 2018 г.

Ошибка spring security на wildfly

При логине в приложение на spring-security выдает следующее сообщение:
{"timestamp":1464679377206,"status":999,"error":"None","message":"No message available"}
и редиректит на error-page в приложении развернутом на wildfly, при запуске spring-boot такой проблемы нет. При чем, если затем вбить правильный адрес руками все работает корректно. Класс запуска приложения:
@SpringBootApplication public class Application extends SpringBootServletInitializer {
public static void main(String[] args) throws Exception { SpringApplication.run(Application.class, args); }
@Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(applicationClass); }
private static Class applicationClass = Application.class;
}
Thymeleaf форма логина