Użycie "ROLE" w Spring Security + zależność klasy @Service od clienta i ID

Odpowiedz Nowy wątek
2014-12-23 23:55
0

Siema
Mam taką zagwozdkę i kopie w google, ale nic nie moge znaleźć, a nawet ciężko mi przychodza pomysly na to co wpisac :)

O co chodzi? Sprawy sa.. chyba dwie albo trzy

  1. Załóżmy ze mam jakies restapi i tam uzytkownikow. Niektore urle z serwisu sa uzaleznione od tego czy user juz przeszedl proces autentykacji (nie chce tego opisywac szczegolowo bo nie ma sensu - ważne jest, ze niektore urle dzialaja jak user bedzie juz mial w bazie zapisany pewien token)
    I teraz czy moge zrobic tak, że w momencie zakonczenia autentykacji przez usera, podczas dodawania tego specjalnego tokenu do bazy, dodam temu userowi nową ROLE (dla springa security) o jakiejś nazwie, przypuśćmy ROLE_TOKEN i ustawic w HttpSecurity (JavaConfig), że do tego urla ma dostep tylko user z np ROLE_USER and ROLE_TOKEN. Co myślicie o takim podejściu?

  2. Druga sprawa poniekąd tycząca się też tego samego problemu, mam klase @Service która w każdej swojej metodzie potrzebuje stworzyc i uzywac clienta jakiegos zewnetrznego api, (cos w stylu FacebookClient, TwitterClient, DropboxClient, YoutubeClient itp - taka klasa 'kliencka')
    Uzywam springa do wstrzykiwania serwisu ktorego uzywam w controllerze. I teraz: do stworzenia tego clienta potrzebuje ID uzytkownika, które zawsze przecież jest inne, więc na sztywno nie przypiszę.
    Chciałbym uniknąć tworzenia tego clienta w każdej metodzie osobno, jakos to 'zglobalizować'. Chciałem przez konstruktor, ale skoro wstrzykuje ten serwis do kontrolera, to w jaki sposob moglbym w kontrolerze podać jakby parametr (w postaci ID wzietego z urla) do konstruktora tego serwisu, żeby cała klasa serwisu używała swoich metod opierając się na tym ID?
    Myslalem też żeby za pomocą AOP przed każdym wywołaniem metody z tego serwisu stworzyc na nowo client według ID - o ile to możliwe

  3. tak btw: Jak juz jestesmy przy cliencie - jak mam metode, ktora tworzy tego clienta i jego tworzenie sie nie powiedzie to lepiej jest zrobic jakis ClientCreationException i nim rzucić, zwrócić null czy Null Object Pattern (chyba sie nie sprawdzi w tym wypadku)?

jak czegos nie rozumieliscie to mowcie, napisze jak bede mogl dokladniej :P
dzieki

edit
gdyby pytanie 1 okazało sie dobrym podejsciem, to drugie mogloby sie rozwiazac bo moglbym dostep do serwisów miec @Secured("ROLE_TOKEN") albo same urle które uzywaja tych serwisów zabezpieczone tak jak to opisalem w pytaniu 1 :)

edytowany 6x, ostatnio: azalut, 2014-12-24 00:16

Pozostało 580 znaków

2014-12-25 01:39
tk
1
azalut napisał(a):

I teraz czy moge zrobic tak, że w momencie zakonczenia autentykacji przez usera, podczas dodawania tego specjalnego tokenu do bazy, dodam temu userowi nową ROLE (dla springa security) o jakiejś nazwie, przypuśćmy ROLE_TOKEN i ustawic w HttpSecurity (JavaConfig), że do tego urla ma dostep tylko user z np ROLE_USER and ROLE_TOKEN.

ROLE_TOKEN to statyczna rola dla uzytkownikow, ktorzy posiadaja jakikolwiek token czy to rola dynamiczna w sensie takim, ze za kazdym razem inna jest nawa roli w zaleznosci od tokenu?

Jezeli dobrze rozumiem to wyglada to tak: najpierw user wchodzi pod url gdzie podaje nazwe uzytkownika i haslo, a dopiero pozniej wchodzi pod interesujacy go url, ktory jest zabezpieczony?

azalut napisał(a):

Co myślicie o takim podejściu?

Jezeli o mnie chodzi to uwazam, ze dostep do zabezpieczonych uslug nie powinien byc poprzedzony wczesniejszym wysylaniem żądania o autoryzacje. Zamiast tworzyc tokeny dodawalbym nazwe uzytkownika i haslo do kazdego zabezpieczonego url-a. No ale ciekaw jestem czy ktos tutaj ma inne zdanie na ten temat i potrafi je uzasadnic :)

Pozostało 580 znaków

2014-12-25 15:25
0

@tk
tak, ROLE_TOKEN to statyczna nazwa, tylko tak wymysliłem dla przykładu (faktycznie można to zle zrozumieć)
Myśle, że troche źle mnie rozumiesz, wygląda to mniej-wiecej tak:
Image and video hosting by TinyPic

We własnym RESTapi mam możliwość (a raczej konieczność) utworzenia konta użytkownika (logowanie/autentykacja itp do mojego restapi swoją drogą). Jak już się je utworzy, to potrzebuje używać zewnętrznego API. I żeby klient mojego serwisu mogl uzywac tego api z zewnątrz, musi również się uwierzytelnić i przechowywać w bazie (mojego serwisu) specjalny klucz, przez który może tego zewnetrznego api używać.

Przypuśćmy sytuacje: klient robi konto "u mnie" (tj. w moim restapi) i posiada konto na dropbox. I teraz: chce np wrzucić coś za pomocą mojej aplikacji na dropbox to potrzebuje od dropboxa wygenerować specjalny klucz, który pozwoli mu wysyłanie/odbieranie plików na dane konto dropboxowe. (dodam, że taki klucz generuje się tylko raz)
I dalej: posiadam w moim RESTapi linki do uploadu/dowloadu plików z konta dropbox. Jesli użytkownik ma konto w moim restapi (troche głupio brzmi ale wiecie ocb :)), ale nie ma tego klucza od dropboxa to on nie pozwoli mu nic wrzucić -> stąd niektóre URLe (np do downloadu/uploadu) chciałbym mieć zabezpieczone: jeśli nie masz klucza od dropboxa to nie możesz ich używać, jak masz klucz to prosze bardzo cos takiego :)
to samo mógłbym powiedziec o facebooku, google drive, jakimś portalu czy innym serwisie, który udostępnia API, do którego dostęp mamy tylko posiadając klucz/token

W tym wypadku to wygląda tak, że tworze obiekt clienta, który w konstruktorze potrzebuje tego tokenu od zewnętrznego serwisu -> przez ten token wie, z którym kontem ma do czynienia i czy w ogóle jest poprawny czy nie

Trudno mi to troche wytłumaczyć :) w praktyce nie jest to takie skomplikowane jak opisywanie tego :D

edytowany 1x, ostatnio: azalut, 2014-12-25 15:26

Pozostało 580 znaków

2014-12-25 19:59
tk
1

OK, to wezmy taka sytuacje, ze mamy kontroler

@RestController
public class MyController {
    @RequestMapping("/dropbox/test")
    public String dropBoxAction() {
        return "Tu akcja odwolujaca sie do DropBox-a";
    }
 
    @RequestMapping("/something/test")
    public String otherAction() {
        return "Tu akcja odwolujaca sie do czegos innego";
    }
}

Rozumiem, ze uzytkownik ktory posiada token DropBox-a powinien miec mozliwosc wejscia pod adres "/dropbox/test". Natomiast jezeli nie posiada tokenu to nie powinien miec takiej mozliwosci, ale powinien miec mozliwosc wejscia pod adres "/something/test"?

Jezeli dobrze rozumiem, to zrobilbym to tak:

  1. Stworzylbym sobie klase roli
public class DropBoxRole implements GrantedAuthority {
    @Override
    public String getAuthority() {
        return "ROLE_DROP_BOX";
    }
}
  1. Utworzylbym service implementujacy interfejs UserDetailsService
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
 
@Service
public class MyUserDetailsProvider implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        // Haslo pobrane z bazy
        String password = "haslo";
        ArrayList<GrantedAuthority> roles = new ArrayList<>();
 
        /*
        Tutaj powinienes odwolac sie do bazy danych aby sprawdzic czy user o podanej nazwie
        posiada token do DopBox-a. Ja sobie to ulatwilem i przyznaje role na podstawie nazwy
        uzytkownika.
         */
        if (userName.equals("bez_drop_boxa")) {
            // Jakies role
        } else if (userName.equals("z_drop_boxem")) {
            // Jakies role + drop box
            roles.add(new DropBoxRole());
        }
 
        return new User(userName, password, roles);
    }
}
  1. Skonfigurowalbym Spring Security w nastepujacy sposob
@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyUserDetailsProvider userDetailsProvider;
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/dropbox/**")
                    .hasRole("DROP_BOX")
                .anyRequest().authenticated();
 
        http.httpBasic();
    }
 
    @Override
    protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsProvider);
        authManagerBuilder.authenticationProvider(authenticationProvider);
    }
 
    @Override
    @Bean(name = "authenticationManager")
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
  1. W usludze klienta najpierw sprawdzalbym jaki uzytkownik przeszedl autoryzacje i na tej podstawie wyciagalbym sobie z bazy token DropBox-a (bez zadnych parametrow w konstruktorze itd).

Po skonfigurowaniu aplikacji w nastepujacy sposob wchodze pod adres "/dropbox/test", loguje sie jako uzytkownik "bez_drop_boxa" z haslem "haslo" i dostaje informacje ze dostep jest zabroniony. Wchodze pod adres "/something/test" i wszystko dziala. Czyszcze cache przegladarki (nawiasem mowiac to pisze tego posta drugi raz bo jak wyczyscilem cache to sie wylogowalem z 4P i po kliknieciu na wyslij utracilem zawartosc posta), ponownie wchodze pod adres "/dropbox/test", podaje nazwe uzytkownika "z_drop_boxem" z haslem "haslo" i dostaje komunikat "Tu akcja odwolujaca sie do DropBox-a". Wchodze pod adres "/something/test" i wszystko dziala. Mam nadzieje, ze o to Ci chodzilo.

edytowany 2x, ostatnio: tk, 2014-12-25 20:02

Pozostało 580 znaków

2014-12-25 23:04
0

dokładnie @tk, bardzo podobnie mam to zrobione u siebie w aplikacji, doszedłem do tego kopiąc google
dobrze, że zrozumiałeś o co mi chodziło

czyli ogólnie rzecz biorąc uważasz, że jeśli mam userów z/bez tokenu od dropboxa, to warto URLe, które używaja tego tokena, zabezpieczyć właśnie nową ROLE?
bo do tej pory te URLe miałem ogólnodostępne i chciałem zrobić coś w stylu: jeśli ma token -> działa, jeśli nie ma -> zwraca np 400 Bad Request albo 403 Forbidden (w sumie jakby zwracalo 403 to wyjdzie na to samo co dodanie nowej ROLE :))
Już po części zrobiłem sobie to o czym piszemy, ale jeszcze troche do zakodowania zostało

[..] (bez zadnych parametrow w konstruktorze itd).

Zewnętrzne api udstępnia klasy, za pomocą których używam tego API. I właśnie w tej klasie która mam dostarczoną konstruktor potrzebuje tego tokena, dlatego nie uknikne tego :P wyciągam -> tworze obiekt Clienta -> robie różne akcje na tym api, czyli pseudo-kodowo tak:

String token = jakiesRepository.pobierzTokenPoIdCzyCzymKolwiek(id);
Client client = new Client(token);
cleint.doSomethingOnClient(someArgument);

Chyba zdecyduje się na zabezpieczenie tych URLi właśnie za pomocą nowej ROLE

Ale miałbym jeszcze pytania 2:
1. Czy da się zabezpieczać klasy adnotowane @Service adnotacją @Secured("ROLE_SOMETHING")?
mógłbym sam sprawdzić, ale liczę na podpowiedzi "czy w ogóle warto" bo może to jakiś anty-pattern :]

2. To samo co w pyt. 3 w temacie: Jak tworze obiekt Client i jego tworzenie sie nie powiedzie z różnych przyczyn np. zły ID użytkownika, zły token itp. to:

  1. zwrócić null
  2. rzucic jakimś customowym ClientCreationException
  3. null object pattern (tylko tu bedzie lipa, bo w koncu client jest taka "very important" klasą, że nullobjectowym obiektem jej nie zastąpie, wiec raczej odpada)

Myśle o tym co zrobić, bo np. w wypadku, gdy podczas tworzenia obiektu Client zawiódł ID użytkownika moglbym jakos poinformowac o tym uzytkownika(np exceptionem customowym jak napisalem wyzej), jeśli zawiedzie token, tez moge mu powiedziec, że zawiódł token itd. W wypadku jakbym zwrocil null to user pomyśli: czemu? - null nie daje informacji
Przez te wyjatki kod moglby stac sie troszke bardziej rozwlekły, bo try-catchow by pare weszło, byc moze i w controllery, ale mysle ze warto, co uważasz/cie?

edytowany 2x, ostatnio: azalut, 2014-12-25 23:07

Pozostało 580 znaków

2014-12-26 01:04
tk
1
azalut napisał(a):

czyli ogólnie rzecz biorąc uważasz, że jeśli mam userów z/bez tokenu od dropboxa, to warto URLe, które używaja tego tokena, zabezpieczyć właśnie nową ROLE?

Moim zdaniem sytuacje tego typu warto oprzec o polityke bezpieczenstwa - jezeli uzytkownik nie ma tokena to nie powinien miec prawa wywolac danej metody.

Natomiast po pewnym namysle doszedlem do wniosku, ze sprawa dyskusyjna jest czy role sa tutaj najlepszym rozwiazaniem. Zamiast sprawdzac role uzytkownikow byc moze warto byloby sprawdzac uprawnienia (hasPermission(tutaj cos tam)). No ale tym sie nie bawilem :)

Innymi slowy: nie wiem czy role sa tutaj do tego najlepsze. Nie mowie ani ze sa najlepsze ani ze nie sa.

azalut napisał(a):

Zewnętrzne api udstępnia klasy, za pomocą których używam tego API. I właśnie w tej klasie która mam dostarczoną konstruktor potrzebuje tego tokena, dlatego nie uknikne tego :P wyciągam -> tworze obiekt Clienta -> robie różne akcje na tym api

W takiej sytuacji stworzylbym sobie serwis DropBox i klase klienta trzymalbym w polu klasy DropBox (ewentualnie DropBoxImpl jezeli DropBox to interfejs). No i w zaleznosci od tego jak ta cala klasa kliencka wyglada albo nie udostepnialbym jej w ogole na zewnatrz i dorobilbym metody realizujace pewne czynnosci z wykorzystaniem tego klienta, albo stworzylbym metode getClient, ktora zwracalaby klienta z przekazanym do konstruktora tokenem (pobranym wczesniej z bazy danych - o to by sie zatroszczyl serwis DropBox). Oczywiscie po utworzeniu takiej klasy nie trzeba byloby tworzyc klienta na nowo - mozna po prostu zwrocic instancje wczesniej utworzonej klasy.

azalut napisał(a):

Czy da się zabezpieczać klasy adnotowane @Service adnotacją @Secured("ROLE_SOMETHING")?
mógłbym sam sprawdzić, ale liczę na podpowiedzi "czy w ogóle warto" bo może to jakiś anty-pattern :]

Nie wiem czy da sie zabezpieczyc klasy ale powinno dac sie zabezpieczac poszczegolne metody. Uwazam, ze taka forma zabezpieczen jest dobra praktyka.

Dodam, ze czasami ciezko zabezpieczyc aplikacje stosujac wylacznie zabezpieczenia na poziomie adresu. Jezeli masz np. funkcjonalnosc odpowiedzialna za zarzadzanie plikami i uzytkownik w zaleznosci od atrybutow (przydzielanych dynamicznie przez admina) pliku ma do niego dostep lub nie ma to powodzenia z robieniem tego na URL-ach.

Swoja droga to nie wiem czy nie lepiej uzywac adnotacji @PreAuthorize - gdzies chyba przeczytalem, ze to nowsza (a co za tym idzie pewnie zalecana) wersja adnotacji @Secured, ale nie chce mi sie juz tego sprawdzac.

azalut napisał(a):

2. To samo co w pyt. 3 w temacie: Jak tworze obiekt Client i jego tworzenie sie nie powiedzie z różnych przyczyn np. zły ID użytkownika, zły token itp. to:

  1. zwrócić null
  2. rzucic jakimś customowym ClientCreationException
  3. null object pattern (tylko tu bedzie lipa, bo w koncu client jest taka "very important" klasą, że nullobjectowym obiektem jej nie zastąpie, wiec raczej odpada)

Jestem za opcja numer 2.

azalut napisał(a):

Przez te wyjatki kod moglby stac sie troszke bardziej rozwlekły, bo try-catchow by pare weszło, byc moze i w controllery, ale mysle ze warto, co uważasz/cie?

Mam nadzieje, ze nie planujesz pisac metod kontrolerow w nastepujacy sposob:

@RequestMapping("/my_url")
    public String myAction() {
        try {
            // Cos
        }
        catch (ClientCreationException ex) {
            // Powiadom uzytkownika o bledzie typu ClientCreationException            
        }
        catch (/* Kolejna klasa wyjatku */) {
            // Powiadom uzytkownika o jakims bledzie
        }
        catch (Exception ex) {
            // Powiadom uzytkownika o nieznanym bledzie
        }
 
        return "Zwracane wyniki";
    }

Jezeli tak sie do tego zabierzesz to kod Ci sie bedzie niepotrzebnie rozrastal, a jak dojdzie Ci w metodzie nowy wyjatek to bedziesz sobie pozniej wszedzie dopisywal obsluge wyjatku. Proponowalbym napisac sobie klase typu:

@ControllerAdvice
public class MyExceptionHandler {
    @ResponseBody
    @ExceptionHandler(ClientCreationException.class)
    public String handleClientCreationException() {
        return "Nie mozna utworzyc klienta";
    }
}

Olewasz wtedy lapanie wyjatkow w akcjach kontrolera - jezeli jakikolwiek kontroler nie zlapie wyjatku to zostanie wywolana metoda handleClientCreationException. Jak dojdzie Ci nowa klasa wyjatku to sobie w tym miejscu dopiszesz nowa metode i bedzie to dzialac dla calej aplikacji. Natomiast jak bedziesz chcial zmienic obsluge jakiegos wyjatku to nie bedziesz musial poprawiac nie wiadomo ilu kontrolerow tylko w jednym miejscu sobie zmienisz kod.

W powyzszym przykladzie poinformowalem uzytkownika o bledzie zwracajac komunikat bledu. W normalnej sytuacji warto byloby zwrocic tez jakis status HTTP, kod bledu itd.

PS.
Mam nadzieje, ze zrozumiales wszystko co chcialem napisac w tej wiadomosci - troche mi sie chce juz spac i nie przylozylem sie specjalnie do formy przekazu.

edytowany 3x, ostatnio: tk, 2014-12-26 10:24

Pozostało 580 znaków

2014-12-26 22:37
0

@tk
apropos tworzenia klienta: jego stworzenie jest generalnie bardzo proste, jednak problem jest jeden: ID uzytkownika
bo tak, tworzenie clienta wyglada mniej-wiecej tak: klient wchodzi pod jakis url /cos/{id} gdzie {id} to nr id, potem to id controller pobiera z urla, podaje do serwisu który tworzy clienta i potem wykonuje jakas funkcjonalnosc. żeby utworzyc tego clienta to potrzebuje ID uzytkownika (bo po ID szuka w bazie tego tokenu, za pomoca ktorego tworzy clienta jak juz powiedzielismy).
I teraz pojawiałby sie problem: mam serwis z np. 2 metodami, obie potrzebuja stworzyc sobie tego clienta, więc potrzebują ID - stąd nie unikne tworzenia clienta w każdej metodzie bo ID będzie się zmieniało - a metoda tylko po ID wie, z którym uzytkownikiem ma do czynienia. Nie moge więc stworzyc jednej, stałej instancji tego clienta, bo w zależności kto uzyje mojego rest serwisu - ten client bedzie wygladal inaczej (bo kazdy user ma inny token)

Mógłbym zrobić coś na zasadzie, ze jak klient wchodzi pod jakis URL, to controller ktory zajmuje się tym URLem ma wstrzykniętą instancje klasy @Service (tej z dwiema metodami) która jako parametr konstruktora przyjmuje ID: wtedy moglbym zrobic pole private Client w klasie @Service i bylbym pewien, że operuje na dobrej instancji - bo w konstruktorze @Service za pomoca tego "wstrzykniętego id" zrobilbym instancje Clienta dla dobrego tokenu i tym samym uniknalbym tworzenia Clienta w każdej metodzie osobno.
Pytanie czy to jest w ogole mozliwe?

"przepływ" wygladalby mniej wiecej tak: client wchodzi pod url -> spring wybiera controller -> analizuje URI -> pobiera z niego ID -> wstrzykuje ten @Service w konstruktorze podajac to ID ktore przed chwila pobral -> wchodzi pod metode z dobrym @RequestMapping -> wykonuje ona metody klasy @Service na instancji "opierajacej sie" na tym wybranym ID
Mam nadzieje ze rozumiesz - tylko nie wiem czy to możliwe jak pisałem :)

to powodzenia z robieniem tego na URL-ach.

to jak wtedy się do tego zabrać?

Jestem za opcja numer 2.

też tak myślę

Mam nadzieje, ze nie planujesz pisac metod kontrolerow w nastepujacy sposob:

prawde mówiąc na początku miałem taką myśl haha, ale to chyba przez to, że najpierw planowalem ten exception łapać w @Service, ale chyba ten sposob który spring oferuje do lapania wyjatkow (to co podales) jest najlepszym rozwiazaniem. BTW: dopiero poznalem adnotacje @ControllerAdvice, wow! :]

Co do kodow statusu itp to wiem wiem, staram sie robic jak najlepiej pod tym wzgledem

A przy okazji krótkie pytanie, jak mam metode w @Controllerze jakimś, która może sie powieść, a może nie i: w wypadku powodzenia zwroci np 201 Created, a w wypadku niepowodzenia np 409 Conflict
Jest taka adnotacja @ResponseStatus gdzie podaje kod statusu jaki zwraca metoda, ale on jest tak jakby.. stały, a chciałbym, żeby metoda zwracala odpowiedni kod statusu, w zaleznosci co dostanie od klas Service. Czy jest lepszy sposób na zwracanie kodu statusu, niz taki:

@RequestMapping(value = "/cos")
@ResponseBody
public String jakasNazwa(HttpServletResponse response){
    boolean powodzenie = service.zrobCosWServiceNpStworzUzytkownikaIPodajTrueFalseCzySieUdalo(Dane dane);
    if(powodzenie)
        response.zwrocStatus201
        return "udalo ci sie, jest super"
    else
        response.zwrocStatus409
        return "nie udalo ci sie, jest nie super"
}

To obecnie zostaje problem tego Clienta. Mowiac krótko: da rade jakos zrobic, żeby każda metoda @Serviceu nie musiala tworzyc clienta na nowo?

edytowany 2x, ostatnio: azalut, 2014-12-26 22:40
Co do drugiego pytania, które zwraca status w zależności od powodzenia, to możesz zwrócić dwa różne wyjątki z service lub jeden z kodem (wew. apki) i złapać go w @ControllerAdvice - NoZi 2014-12-27 00:38

Pozostało 580 znaków

2014-12-27 17:41
tk
1

Moim zdaniem to powinno wygladac tak:

  1. Zanim wywola sie akcja w kontrolerze uzytkownik powinien przejsc przez uwierzytelnianie. Nie wiem jak dokladnie chcesz to zrobic, ale wspominales cos o id w URL-u (rozumiem ze haslo tez jest jakos przesylane) wiec moze to byc akurat tak (choc nie musi). Kontroler wiec nawet nie bardzo potrzebuje tego id uzytkownika - bez niego spring nie powinien dopuscic do wywolania metody w kontrolerze bo przeciez adres, do ktorego sie odwoluje ta metoda jest zabezpieczony przed nieautoryzowanym dostepem. Natomiast jak sie mu poda to Id to ta informacja jest juz srednio potrzebna kontrolerowi bo on uzytkownika nie autoryzuje.

  2. Jezeli uzytkownik zostanie uwierzytelniony to zarowno kontroler jak i serwis moze miec dostep do informacji jaki to uzytkownik. Wystarczy, ze sie dostaniesz do kontekstu Spring Security, a zrobisz to chocby przez wywolanie metody statycznej getContext() na klasie SecurityContextHolder, czyli

SecurityContextHolder.getContext();

Dane uzytkownika pewnie wydobedziesz z obiektu typu Authentication:

Authentication authentication = SecurityContextHolder.getContext()
                    .getAuthentication();
authentication.getPrincipal(); // tutaj powinny byc interesujace Cie dane

Wiec wystarczy, ze w Twoim serwisie DropBox utworzysz sobie metode createClient(), ktora pobierze sobie nazwe uzytkownika, nastepnie pobierze sobie token DropBox-a z bazy danych i na podstawie tych danych utworzy instancje klienta DropBox. Pozniej jak chcesz np wyslac cos na drop boxa to do tego serwisu DropBox sobie dopiszesz metode sendFileToDropBox, w niej wywolasz metode createClient i na utworzonym kliencie bedziesz sobie juz cos tam robil.

Jezeli nie chcesz przy kazdym uzyciu API tworzyc od nowa klienta (co jest calkowicie sluszne) to zawsze mozesz zrobic cos takiego:

@Service
@Scope(value = "request")
public class DropBox {
    private DropBoxClient client;
 
    public void doSomething() {
        this.getClient().doSomething();
    }
 
    private DropBoxClient getClient() {
        if (client != null) {
            return client;
        }
 
        Authentication authentication = SecurityContextHolder.getContext()
                .getAuthentication();
        authentication.getPrincipal();
 
        // Pobieranie danych uzytkownika z obiektu authentication oraz tokenu DropBox
 
        return new DropBoxClient("token");
    }
}

Adnotacja @Scope(value = "request") powinna zapewnic tworzenie nowej instancji serwisu przy kazdym wywolaniu Twojego REST-a (ale nie przy kazdym odwolaniu sie do serwisu). Przy pierwszym wywolaniu metody getClient zostanie utworzona instancja klienta a juz przy kolejnych zwrocona istniejaca instancja obiektu. Dzieki adnotacji @Scope nie powinno byc tutaj problemu z utworzeniem instancji obiektu klienta z tokenem nalezacym do innego uzytkownika.

Troche leszym pomyslem jest utworzenie sobie dedykowanego serwisu, ktory bedzie mial metode zwracajaca aktualnego uzytkownika (nie ma sensu odwolywac sie do kontekstu springa w kazdej klasie obslugujacej jakies API - lepiej byloby to uproscic).

Dodam, ze w serwisie DropBox mozna kombinowac z wykorzystaniem metody @PostConstruct - tzn moglbys sobie stworzyc jakas metode tworzaca klienta po utworzeniu serwisu zamiast robic to w metodzie getClient.

azalut napisał(a):

to jak wtedy się do tego zabrać?

No wlasnie mozna to zrobic np. za pomoca adnotacji @PreAuthorize - podasz tam odpowiednie wyrazenie, ktore sprawdzi czy uzytkownik moze wykonac jakas czynnosc czy nie. Wyrazenia, o ktorych mowa moga byc bardziej skomplikowane niz sprawdzenie roli. Moga sprawdzac pozwolenia (permission), wywolywac metody serwisow, ktore moga zweryfikowac to i owo i tego typu rzeczy. Dodam, ze to pewnie nie jedyna metoda.

azalut napisał(a):

Czy jest lepszy sposób na zwracanie kodu statusu, niz taki:

Zdaje sie, ze odpowiedzi udzielil juz ktos w komentarzu.

edytowany 4x, ostatnio: tk, 2014-12-27 17:57

Pozostało 580 znaków

2014-12-30 12:30
0

nie miałem ostatnio czasu, żeby odpisać @tk :P

ahh.. jak człowiek sobie czasem nie zdaje sprawy z niektórych rzeczy.. :D dokładnie to co napisałeś używam już w swojej aplikacji, ale za nic na myśl mi nie wpadło, żeby z kontekstu pobrać usera

czyli mówiąc krótko, konwencja, że moje konta będą np pod account/{id} zdaje się być zbędna, bo moge zrobić jeden serwis, którym zwróce sobie dane o osobie, która aktualnie jest uwierzytelniona?
Teraz stoje przed wyborem - zostawić tak jak jest, czy lepiej stworzyć ten serwis o którym wspomniałem wyżej i w zupełności na nim polegać.. hmm..

Bo np. jakbym miał metode która zapisuje do bazy token danemu userowi, to jej deklaracja wygladała do tej pory jakoś tak:

public boolean addTokenToDB(final Long id, final String token){ //code..}

i wtedy wywołanie to by było coś:

service.addTokenToDB(id, token);

co znaczy "userowi z tym id, przypisz ten token"
a gdybym nie podawał tego ID jako parametr tylko pobrał z kontekstu juz wewnatrz tej metody, to znaczyło by "zapisz token" - więc mogłoby gdzieś potem zmniejszyć czytelność kodu, bo nie mówie bezpośrednio komu zapisać.. a może się mylę :D bo gdyby cały restserwis opierał się na konwencji, że ID pobieram z kontekstu, to miałoby chyba sens, jak myślisz/cie?

co do adnotacji @Scope to ciekawe, musze o tym poczytać

Jeśli chodzi o zwracanie kodu statusu, to ten sposób który podał @NoZi jest przyjęty jako często używany (tzw dobra praktyka)? Tak sobie myślę, że jeśli chodzi o aplikacje webowe w springu to wyjątki najlepiej chyba słać jak najwyżej tzn przez repository, service itp aż do controllera i to właśnie jemu pozwolić się zająć obsługą. Bo jak gdzies w warstwie service pojawi się exception to i tak controller jakos na to musi zareagować

BTW
zastanawia mnie, dlaczego context.getPrincipal() zwraca Object a nie Principal :)

Pozostało 580 znaków

2014-12-31 15:23
tk
1
azalut napisał(a):

czyli mówiąc krótko, konwencja, że moje konta będą np pod account/{id} zdaje się być zbędna, bo moge zrobić jeden serwis, którym zwróce sobie dane o osobie, która aktualnie jest uwierzytelniona?

To zalezy czy chcesz pobrac dane uzytkownika uwierzytelnionego czy dowolnego. Jezeli takiego, ktory sie uwierzytelnil to podawanie w adresie identyfikatora jest zbedne, pod warunkiem ze nie sluzy on wlasnie do tego aby uzytkownika uwierzytelnic (w praktyce do uwierzytelniania uzywa sie czesciej nazwy uzytkownika i hasla, niekoniecznie podawanego w adresie url).

azalut napisał(a):

Teraz stoje przed wyborem - zostawić tak jak jest, czy lepiej stworzyć ten serwis o którym wspomniałem wyżej i w zupełności na nim polegać.. hmm..

Zrob jak uwazasz za sluszne :)

azalut napisał(a):

Bo np. jakbym miał metode która zapisuje do bazy token danemu userowi, to jej deklaracja wygladała do tej pory jakoś tak:

public boolean addTokenToDB(final Long id, final String token){ //code..}

i wtedy wywołanie to by było coś:

service.addTokenToDB(id, token);

co znaczy "userowi z tym id, przypisz ten token"
a gdybym nie podawał tego ID jako parametr tylko pobrał z kontekstu juz wewnatrz tej metody, to znaczyło by "zapisz token"

Czasami robie sobie metody w dwoch wersjach - jedna z id a druga bez id. Ta pierwsza realizuje cala funkcjonalnosc a ta druga wywoluje pierwsza podstawiajac za id identyfikator uwierzytelnionego uzytkownika. Latwo sie domyslec po co sa dwie wersje (o ile nazwa parametru to userId a nie id) a jednoczesnie jestem przygotowany na sytuacje typu "aplikacja byla projektowana tak aby uzytkownik mogl edytowac tylko swoje zasoby a pozniej doszla rola o nazwie adminstrator i ten administrator moze edytowac nie tylko swoje zasoby ale takze zasoby innych uzytkownikow".

azalut napisał(a):

Tak sobie myślę, że jeśli chodzi o aplikacje webowe w springu to wyjątki najlepiej chyba słać jak najwyżej tzn przez repository, service itp aż do controllera i to właśnie jemu pozwolić się zająć obsługą. Bo jak gdzies w warstwie service pojawi się exception to i tak controller jakos na to musi zareagować

Klasa oznaczona adnotacja @ControlerAdvice moglaby sie tym zajac (ewentualnie sam kontroler jezeli z jakis powodow jest to lepsze rozwiazanie). Warto jednak zwrocic uwage na to, ze istnieja sytuacje kiedy na rzucony wyjatek mozna zareagowac i naprawic przyczyne problemu - jezeli takie cos da sie zrobic to moze i warto obsluzyc ten wyjatek w innych warstwach niz wspomniana wyzej klasa. Z drugiej strony jest to czasem przerost formy nad trescia.

azalut napisał(a):

BTW
zastanawia mnie, dlaczego context.getPrincipal() zwraca Object a nie Principal :)

Pewnie dlatego, ze nie wiadomo co tym principalem bedzie. Wiekszosc rozwiazan dziala w oparciu o autoryzacje na zasadzie "podaj uzykownika i haslo" ale nie zawsze tak musi byc. Czasami np. informacje zwiazane z nazwa uzytkownika moga byc zbedne, ale za to potrzebne beda informacje typu adres IP.

Pozostało 580 znaków

2015-01-01 21:30
0

Czasami robie sobie metody w dwoch wersjach - jedna z id a druga bez id.

Nie okaże się, że to troche redundant code? chociaż tak de-facto ta pierwsza metoda bez parametru to defacto jedna linijka pobranie ID z serwisu i Od razu przedelegowanie tego do tej samej metody z ID

Jesli chodzi o obsługę expcetionów to mam to mysle tak samo

Pewnie dlatego, ze nie wiadomo co tym principalem bedzie.[..]

Hmm no tak.. tylko jeśli to zwraca Object to w takim razie gdzie tu przydatność tej metody? sprawdzać jakimś ifem czy context.getPrincipal() instanceof costam? :D bo z Object'u to mało można wziąć, a dokładniej to chyba nic ciekawego

Zrob jak uwazasz za sluszne :)

chyba zdecyduje się stworzyć serwis który mi poda ID z kontekstu, a metody które nie wymagaja uwierzytelniania albo domyslnie są nastawione na szukanie po ID, tak jak mowiles, zostawie z parametrem userId

Każdym postem mi tyle pomogłeś, że sobie sprawy nie zdajesz :) jak będziesz w okolicach Kołobrzegu zapraszam na piwo w podzięce :D

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0