Форум программистов, компьютерный форум, киберфорум
Java: Spring, Spring Boot
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.83/29: Рейтинг темы: голосов - 29, средняя оценка - 4.83
102 / 102 / 40
Регистрация: 24.01.2014
Сообщений: 1,242

Oauth2 authentication with spring security

20.01.2017, 16:56. Показов 5789. Ответов 8
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Доброго времени суток, я пытаюсь сделать на своем сайте авторизацию с помощью других сервисов. На данный момент есть google, facebook и vk. С google все работает отлично, facebook присылает в ответ почему-то только имя пользователя, в том время как vk ничего вообще не присылает, подскажите, как получать данные о пользователях с facebook и vk ?

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package spring.hotel.config;
 
@SpringBootApplication
@Controller
public class Application {
    @Autowired
    private OAuth2ClientContext oauth2ClientContext;
 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
 
    @Bean
    public OAuth2ClientContext oAuth2ClientContext(){
        return oauth2ClientContext;
    }
 
    @RequestMapping(value = {"/"}, method = RequestMethod.GET)
    public String defaultPage() {
        return "index";
    }
}
Java
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
@Configuration
@EnableWebSecurity
@EnableOAuth2Client
@EnableAuthorizationServer
@ComponentScan(value = "spring.hotel")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    OAuth2ClientContext oauth2ClientContext;
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/**").authorizeRequests().antMatchers("/", "/login**", "/webjars/**").permitAll().anyRequest()
                .authenticated().and().exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"))
                .and().logout().logoutSuccessUrl("/").permitAll().and().csrf().csrfTokenRepository(csrfTokenRepository()).and()
                .addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
                .addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
    }
 
    @Bean
    @ConfigurationProperties("facebook.client")
    public AuthorizationCodeResourceDetails facebook() {
        return new AuthorizationCodeResourceDetails();
    }
 
    @Bean
    @ConfigurationProperties("facebook.resource")
    public ResourceServerProperties facebookResource() {
        return new ResourceServerProperties();
    }
 
    @Bean
    @ConfigurationProperties("google.client")
    public AuthorizationCodeResourceDetails google() {
        return new AuthorizationCodeResourceDetails();
    }
 
    @Bean
    @ConfigurationProperties("google.resource")
    public ResourceServerProperties googleResource() {
        return new ResourceServerProperties();
    }
 
    @Bean
    @ConfigurationProperties("vk.client")
    public AuthorizationCodeResourceDetails vk() {
        return new AuthorizationCodeResourceDetails();
    }
 
    @Bean
    @ConfigurationProperties("vk.resource")
    public ResourceServerProperties vkResource() {
        return new ResourceServerProperties();
    }
 
    @Bean
    public FilterRegistrationBean oauth2ClientFilterRegistration(
            OAuth2ClientContextFilter filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(filter);
        registration.setOrder(-100);
        return registration;
    }
 
    private Filter csrfHeaderFilter() {
        return new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {
                CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
                if (csrf != null) {
                    Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
                    String token = csrf.getToken();
                    if (cookie == null || token != null && !token.equals(cookie.getValue())) {
                        cookie = new Cookie("XSRF-TOKEN", token);
                        cookie.setPath("/");
                        response.addCookie(cookie);
                    }
                }
                filterChain.doFilter(request, response);
            }
        };
    }
 
    private CsrfTokenRepository csrfTokenRepository() {
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName("X-XSRF-TOKEN");
        return repository;
    }
 
    private Filter ssoFilter() {
        CompositeFilter filter = new CompositeFilter();
        List<Filter> filters = new ArrayList<Filter>();
 
        OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/facebook");
        OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext);
        facebookFilter.setRestTemplate(facebookTemplate);
        UserInfoTokenServices tokenServices = new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId());
        tokenServices.setRestTemplate(facebookTemplate);
        facebookFilter.setTokenServices(tokenServices);
        filters.add(facebookFilter);
 
        OAuth2ClientAuthenticationProcessingFilter googleFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/google");
        OAuth2RestTemplate googleTemplate = new OAuth2RestTemplate(google(), oauth2ClientContext);
        googleFilter.setRestTemplate(googleTemplate);
        tokenServices = new UserInfoTokenServices(googleResource().getUserInfoUri(), google().getClientId());
        tokenServices.setRestTemplate(googleTemplate);
        googleFilter.setTokenServices(tokenServices);
        filters.add(googleFilter);
 
        OAuth2ClientAuthenticationProcessingFilter vkFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/vk");
        OAuth2RestTemplate vkTemplate = new OAuth2RestTemplate(vk(), oauth2ClientContext);
        vkFilter.setRestTemplate(vkTemplate);
        tokenServices = new UserInfoTokenServices(vkResource().getUserInfoUri(), vk().getClientId());
        tokenServices.setRestTemplate(vkTemplate);
        vkFilter.setTokenServices(tokenServices);
        filters.add(vkFilter);
 
        filter.setFilters(filters);
        return filter;
 
    }
}
 
public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}
Java
1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class ClientController {
 
    @RequestMapping(value = { "/user", "/me"}, method = RequestMethod.GET)
    public Map<String, String> user(Principal principal) {
        Map<String, Object> details = (Map<String, Object>) ((OAuth2Authentication) principal).getUserAuthentication().getDetails();
        Map<String, String> map = new LinkedHashMap<>();
        map.put("name", (String) details.get("name"));
        map.put("email", (String) details.get("email"));
        return map;
    }
}
XML
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
facebook:
  client:
    id: facebook
    clientId: xxx
    clientSecret: xxx
    accessTokenUri: [url]https://graph.facebook.com/oauth/access_token[/url]
    userAuthorizationUri: [url]https://www.facebook.com/dialog/oauth[/url]
    tokenName: oauth_token
    authenticationScheme: query
    clientAuthenticationScheme: form
    scope: email
  resource:
    userInfoUri: [url]https://graph.facebook.com/me[/url]
vk:
  client:
    id: vk
    clientId: xxx
    clientSecret: xxx
    accessTokenUri: [url]https://oauth.vk.com/access_token[/url]
    userAuthorizationUri: [url]https://oauth.vk.com/authorize[/url]
    tokenName: code
    authenticationScheme: query
    clientAuthenticationScheme: form
    scope: email
  resource:
    userInfoUri: [url]https://api.vk.com/method/users.get[/url]
google:
  client:
    id: google
    clientId: xxx
    clientSecret: xxx
    accessTokenUri: [url]https://accounts.google.com/o/oauth2/token[/url]
    userAuthorizationUri: [url]https://accounts.google.com/o/oauth2/auth[/url]
    clientAuthenticationScheme: form
    scope: profile email
  resource:
    userInfoUri: [url]https://www.googleapis.com/userinfo/v2/me[/url]
0
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
20.01.2017, 16:56
Ответы с готовыми решениями:

Авторизация spring oauth2
Добрый вечер. Начинаю разбираться со spring security, rest и и так далее Есть стандартная авторизация с использованием кастомного...

Google OAuth2 и spring rest
Всем привет, понадобилось мне сделать приложенице: рест сервис с аутентификацией + клиент на андройде. Андройд пользователя решил...

Jetty embedded + Spring MVC + Spring Security
Добрый день. По роду работы приходилось писать на JavaSE, в том числе и сложные клиент/серверные программы. Использовал Netty, Apache...

8
Эксперт Java
 Аватар для KEKCoGEN
2399 / 2224 / 565
Регистрация: 28.12.2010
Сообщений: 8,672
20.01.2017, 20:20
Fene4ka_, смотрите в дебаг на ClientController что там приходит. Возможно VK шлет свои данные с другими ключами.

Так же вводит в заблуждение что у вас вообще есть маппинг на /me ... вобщем смотрите в дебаге на тот класс, который ходит на https://api.vk.com/method/users.get и ищите что там куда кладут.
1
102 / 102 / 40
Регистрация: 24.01.2014
Сообщений: 1,242
21.01.2017, 11:09  [ТС]
KEKCoGEN, access token мне доходит успешно, есть подозрение, что для вк нужен users.get с параметрами, но я не точно уверен. На сам ClientController доходят details с единственным элементом в коллекции... в случае с google этих элементов 9 ...
0
Эксперт Java
 Аватар для KEKCoGEN
2399 / 2224 / 565
Регистрация: 28.12.2010
Сообщений: 8,672
21.01.2017, 13:50
Цитата Сообщение от Fene4ka_ Посмотреть сообщение
access token мне доходит успешно
вам нуже не аксес токен, а /me url... Попробуйте сделать запрос на /me сами с какого нибудь rest clientа браузера и посмотрите что приходит.
1
102 / 102 / 40
Регистрация: 24.01.2014
Сообщений: 1,242
21.01.2017, 14:19  [ТС]
Цитата Сообщение от KEKCoGEN Посмотреть сообщение
вам нуже не аксес токен, а /me url... Попробуйте сделать запрос на /me сами с какого нибудь rest clientа браузера и посмотрите что приходит.
я же писал выше, что если сделать запрос на /user или /me, то в случае с google мне приходит Principal, который содержит все необходимые details. В случае с vk этого не происходит, собственно, для того, чтобы работало с vk нужно отредактировать ссылку на получение инфы - https://api.vk.com/method/users.get. Должно быть https://api.vk.com/method/users.get?uids=[Тут id пользователя, который необходимо достать из access token] в таком случае инфа вся приходит как надо.
так вот
Java
1
2
3
4
5
6
7
OAuth2ClientAuthenticationProcessingFilter vkFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/vk");
        OAuth2RestTemplate vkTemplate = new OAuth2RestTemplate(vk(), oauth2ClientContext);
        vkFilter.setRestTemplate(vkTemplate);
        tokenServices = new UserInfoTokenServices(vkResource().getUserInfoUri(), vk().getClientId());
        tokenServices.setRestTemplate(vkTemplate);
        vkFilter.setTokenServices(tokenServices);
        filters.add(vkFilter);
достаточно здесь сформировать правильный UserInfoUri и будет все работать, но как доставать uids из accessToken на этом этапе ?
0
Эксперт Java
 Аватар для KEKCoGEN
2399 / 2224 / 565
Регистрация: 28.12.2010
Сообщений: 8,672
21.01.2017, 15:45
Fene4ka_, по обрывкам кода сложно сказать что у вас там происходит.
Насколько я понял у вас проблема в имплементации запроса на /me url (в случае vk users.get).
Если не брать во внимание всякие штуки которые делает спринг, то это простой GET запрос на url с хедером Authorization: Bearer <token>.
То есть вам надо найти тот класс в спринге который отвечает за этот реквест и дать ему кастомную имплементацию в случае с VK
1
102 / 102 / 40
Регистрация: 24.01.2014
Сообщений: 1,242
21.01.2017, 16:12  [ТС]
KEKCoGEN, вероятно вы правы, ток я так еще делать не умею (
0
Эксперт Java
 Аватар для KEKCoGEN
2399 / 2224 / 565
Регистрация: 28.12.2010
Сообщений: 8,672
21.01.2017, 16:43
Fene4ka_, самое время научиться)
0
102 / 102 / 40
Регистрация: 24.01.2014
Сообщений: 1,242
21.01.2017, 17:02  [ТС]
KEKCoGEN, кажется я все понял, что мне нужно сделать, вскоре реализую и скину сюда, хочу, чтобы вы посмотрели на результат работы)

Добавлено через 11 минут
получилось как-то так, правда это немного кривова-то, наверное)
Java
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
public class UserInfoTokenServicesForVk implements ResourceServerTokenServices {
 
 
    protected final Log logger = LogFactory.getLog(getClass());
 
    private String userInfoEndpointUrl;
 
    private final String clientId;
 
    private OAuth2RestOperations restTemplate;
 
    private String tokenType = DefaultOAuth2AccessToken.BEARER_TYPE;
 
    private AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor();
 
    private PrincipalExtractor principalExtractor = new FixedPrincipalExtractor();
 
    public UserInfoTokenServicesForVk(String userInfoEndpointUrl, String clientId) {
        this.userInfoEndpointUrl = userInfoEndpointUrl;
        this.clientId = clientId;
    }
 
    public String getUserInfoEndpointUrl() {
        return userInfoEndpointUrl;
    }
 
    public void setUserInfoEndpointUrl(String userInfoEndpointUrl) {
        this.userInfoEndpointUrl = userInfoEndpointUrl;
    }
 
    public void setTokenType(String tokenType) {
        this.tokenType = tokenType;
    }
 
    public void setRestTemplate(OAuth2RestOperations restTemplate) {
        this.restTemplate = restTemplate;
    }
 
    public void setAuthoritiesExtractor(AuthoritiesExtractor authoritiesExtractor) {
        Assert.notNull(authoritiesExtractor, "AuthoritiesExtractor must not be null");
        this.authoritiesExtractor = authoritiesExtractor;
    }
 
    public void setPrincipalExtractor(PrincipalExtractor principalExtractor) {
        Assert.notNull(principalExtractor, "PrincipalExtractor must not be null");
        this.principalExtractor = principalExtractor;
    }
 
    @Override
    public OAuth2Authentication loadAuthentication(String accessToken)
            throws AuthenticationException, InvalidTokenException {
        Map<String, Object> map = getMap(this.userInfoEndpointUrl, accessToken);
        if (map.containsKey("error")) {
            this.logger.debug("userinfo returned error: " + map.get("error"));
            throw new InvalidTokenException(accessToken);
        }
        return extractAuthentication(map);
    }
 
    private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
        Object principal = getPrincipal(map);
        List<GrantedAuthority> authorities = this.authoritiesExtractor
                .extractAuthorities(map);
        OAuth2Request request = new OAuth2Request(null, this.clientId, null, true, null,
                null, null, null, null);
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
                principal, "N/A", authorities);
        token.setDetails(map);
        return new OAuth2Authentication(request, token);
    }
 
    /**
     * Return the principal that should be used for the token. The default implementation
     * delegates to the {@link PrincipalExtractor}.
     * @param map the source map
     * @return the principal or {@literal "unknown"}
     */
    protected Object getPrincipal(Map<String, Object> map) {
        Object principal = this.principalExtractor.extractPrincipal(map);
        return (principal == null ? "unknown" : principal);
    }
 
    @Override
    public OAuth2AccessToken readAccessToken(String accessToken) {
        throw new UnsupportedOperationException("Not supported: read access token");
    }
 
    @SuppressWarnings({ "unchecked" })
    private Map<String, Object> getMap(String path, String accessToken) {
        this.logger.info("Getting user info from: " + path);
        try {
            OAuth2RestOperations restTemplate = this.restTemplate;
            if (restTemplate == null) {
                BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
                resource.setClientId(this.clientId);
                restTemplate = new OAuth2RestTemplate(resource);
            }
            OAuth2AccessToken existingToken = restTemplate.getOAuth2ClientContext()
                    .getAccessToken();
            if (existingToken == null || !accessToken.equals(existingToken.getValue())) {
                DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(
                        accessToken);
                token.setTokenType(this.tokenType);
                restTemplate.getOAuth2ClientContext().setAccessToken(token);
            }
            return restTemplate.getForEntity(path, Map.class).getBody();
        }
        catch (Exception ex) {
            this.logger.info("Could not fetch user details: " + ex.getClass() + ", "
                    + ex.getMessage());
            return Collections.<String, Object>singletonMap("error",
                    "Could not fetch user details");
        }
    }
}
Java
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
public class VkCustomFilter extends AbstractAuthenticationProcessingFilter {
 
    public OAuth2RestOperations restTemplate;
    private UserInfoTokenServicesForVk tokenServices;
 
    public VkCustomFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
    }
 
    public void setTokenServices(UserInfoTokenServicesForVk tokenServices) {
        this.tokenServices = tokenServices;
    }
 
    public void setRestTemplate(OAuth2RestOperations restTemplate) {
        this.restTemplate = restTemplate;
    }
 
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        OAuth2AccessToken accessToken;
        try {
            accessToken = restTemplate.getAccessToken();
//            if (accessToken != null){
//                Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//                Map<String, Object> details = (Map<String, Object>) auth.getDetails();
//                details.put("email", accessToken.getAdditionalInformation().get("email"));
//            }
        } catch (OAuth2Exception e) {
            BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);
            publish(new OAuth2AuthenticationFailureEvent(bad));
            throw bad;
        }
        try {
            String userInfoEndpointUrl = tokenServices.getUserInfoEndpointUrl() + "?uids=" + accessToken.getAdditionalInformation().get("user_id").toString();
            tokenServices.setUserInfoEndpointUrl(userInfoEndpointUrl);
            OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue());
            if (authenticationDetailsSource!=null) {
                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
                result.setDetails(authenticationDetailsSource.buildDetails(request));
            }
            publish(new AuthenticationSuccessEvent(result));
            return result;
        }
        catch (InvalidTokenException e) {
            BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);
            publish(new OAuth2AuthenticationFailureEvent(bad));
            throw bad;
        }
    }
 
    private void publish(ApplicationEvent event) {
        if (eventPublisher!=null) {
            eventPublisher.publishEvent(event);
        }
    }
}
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
21.01.2017, 17:02
Помогаю со студенческими работами здесь

Spring + Basic Authentication -- Retrofit 2
Всем доброго... Воюю с настройка авторизации Spring + Basic Authentication на сервере и Retrofit 2 на клиенте... Пака что...

JWT Authentication using Spring Boot
Всем привет. Имеется spring boot приложение c jwt авторизацией/аутентификацией (spring secuity). Оно работает, делал его по туториалу....

Spring. Тесты и Spring-security
Вопрос из области почему так. Есть у меня такой вот тест: @ContextConfiguration(locations =...

Spring security c 3 на 4
Не знаю почему, но после того, как перешел на spring security 4 возникает проблема. Захожу на страницу логина, ввожу имя и пароль....

Spring security
Пытаюсь сделать spring MVC + spring security. При запуске пишет Type Exception Report Message No bean named 'springSecurityFilterChain'...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
9
Ответ Создать тему
Новые блоги и статьи
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Как объединить две одинаковые БД Access с разными данными
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru