Hur navigerar man runt med Spring REST API på ett snyggt sätt?

Permalänk

Hur navigerar man runt med Spring REST API på ett snyggt sätt?

Denna fråga är till perfektionister. Jag har idag för första gången satt mig ned och börjat programmera med lite Spring och jag måste säga att jag uppskattar verkligen @Bean och @AutoWiring annoteringarna. Livet blev så mycket lättare med "Goto"-java kan man enkelt uttryligt säga.

Jag har märkt att det verkar finnas flera olika sätt hur att naigera runt med en REST API i Spring och nu vänder jag mig till er duktiga som vet den goda praktiken.

Låt oss säga att jag har skapat denna SpringSecurity konfigurationen. Denna kod säger att vi använder oss av JDBC databas, vi sätter rättigheter beroende på om man är ADMIN eller USER. Sedan har vi skapat ett objekt som krypterar lösenord. Detta objekt ska vi återanvända då jag har satt @Bean på den som annotering.

@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired private DataSource dataSource; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.jdbcAuthentication() .dataSource(dataSource) .passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic() .and() .authorizeRequests() // Normal users .antMatchers(HttpMethod.GET, "/user/**").hasRole("USER") // Administrator .antMatchers(HttpMethod.GET, "/user/**").hasRole("ADMIN") .antMatchers(HttpMethod.POST, "/user/**").hasRole("ADMIN") .antMatchers(HttpMethod.PUT, "/user/**").hasRole("ADMIN") .antMatchers(HttpMethod.PATCH, "/user/**").hasRole("ADMIN") .antMatchers(HttpMethod.DELETE, "/user/**").hasRole("ADMIN") .and() .csrf().disable() .formLogin().disable(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }

Där efter har jag skapat lite databas också till mig. Enkelt att förstå denna kod. Här skapar jag alltså en datakälla vid namn dataSource och jag skapar även två tabeller.

@Configuration @EnableTransactionManagement public class HibernateConfig { private static final String USERNAME = "admin"; private static final String PASSWORD = "password"; private static final String DRIVER = "com.mysql.cj.jdbc.Driver"; private static final String ADDRESS = "jdbc:mysql://localhost:3306"; private static final String DATABASE = "MyDatabase"; private DriverManagerDataSource dataSource; @Bean public DataSource dataSource() { dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(DRIVER); dataSource.setUsername(USERNAME); dataSource.setPassword(PASSWORD); dataSource.setUrl(ADDRESS + "/" + DATABASE + "?createDatabaseIfNotExist=true"); return dataSource; } @Bean public JdbcTemplate createTables() { // The standard JDBC implementation of the UserDetailsService String userTableQuery = "create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(50) not null,enabled boolean not null)"; String authoritiesTableQuery = "create table authorities (username varchar_ignorecase(50) not null,authority varchar_ignorecase(50) not null,constraint fk_authorities_users foreign key(username) references users(username))"; String index = "create unique index ix_auth_username on authorities (username,authority)"; JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.execute(userTableQuery); jdbcTemplate.execute(authoritiesTableQuery); jdbcTemplate.execute(index); return jdbcTemplate; } }

Dessa funktioner ovan anropas när Spring Boot startas upp. Åter igen så kommer mina @Beans till nytta.

SpringBootApplication public class JWiFiLoggerServerApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(JWiFiLoggerServerApplication.class, args); // First create a database if not created context.getBean(HibernateConfig.class).dataSource(); // Then create the tables if they are not created context.getBean(HibernateConfig.class).createTables(); } }

Och nu till min fråga. Som du ser ovan så har jag satt olika begränsningar på vem som får gå vart...eller rättare sakt, vem som får skicka data eller hämta data. Denna kod har jag rakt kopierat av utan att direkt förstå något om allt är nödvändigt eller inte. Jag har förståelse vad alla .antMatchers() gör.

@Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic() .and() .authorizeRequests() // Normal users .antMatchers(HttpMethod.GET, "/user/**").hasRole("USER") // Administrator .antMatchers(HttpMethod.GET, "/user/**").hasRole("ADMIN") .antMatchers(HttpMethod.POST, "/user/**").hasRole("ADMIN") .antMatchers(HttpMethod.PUT, "/user/**").hasRole("ADMIN") .antMatchers(HttpMethod.PATCH, "/user/**").hasRole("ADMIN") .antMatchers(HttpMethod.DELETE, "/user/**").hasRole("ADMIN") .and() .csrf().disable() .formLogin().disable(); }

Fråga:
Men om jag vill naviera runt i min REST API, dvs anropa och hämta data i form av respons. Då använder jag mig av följande. Finns det något annat sätt att göra för att få det ännu bättre? Jag tänkte mest på HttpMethod.X konstanterna som jag använder som argument. Behövs dom?

Hur hade du gjort? Mitt mål till att börja med är att jag ska kunna skicka ett helt Java-objekt via internet, från server till klient, eller tvärt om.

@RestController @RequestMapping("login") public class LoginController { @Autowired private PasswordEncoder passwordEncoder; @RequestMapping("login/{userName}/{password}") public void login(@PathVariable("userName") String userName, @PathVariable("password") String password) { } }

Permalänk

Jag har några frågor.

Om jag ansluter "http://localhost:8080/login?userName=mittNamn&password=mittLosen"
Visst betyder det att jag skickar mitt användarnamn och lösenord då?

@RequestMapping(value = "/login", params = { "userName", "password" }, method = RequestMethod.POST) public void login(@PathVariable("userName") String userName, @PathVariable("password") String password) { // Vi har nu skickat userName och password frn våran klient }

Om jag vill skicka ett objekt från klient till server

@RequestMapping(value = "/login", method = RequestMethod.GET) public void login(@RequestBody LoginForm loginForm) { // Vi har nu skickat objektet lognForm }

Där våran klass LoginForm finns på både klient och server.

Om jag vill ha en återkoppling från min server så skriver jag

@RequestMapping(value = "/login", method = RequestMethod.POST) @ResponseBody public String login() { return "Återkoppling"; }

Eller om jag skickar ett objekt

@RequestMapping(value = "/login", method = RequestMethod.GET) @ResponseBody public MitObjekt login() { return new MittObjekt(); }

Där klassen MittObjekt finns både hos server och klient.

Har jag rätt? Eller har jag fel? Finns det smartare sätt att skriva?

Permalänk
Medlem
Skrivet av heretic16:

Jag har några frågor.

Om jag ansluter "http://localhost:8080/login?userName=mittNamn&password=mittLosen"
Visst betyder det att jag skickar mitt användarnamn och lösenord då?

@RequestMapping(value = "/login", params = { "userName", "password" }, method = RequestMethod.POST) public void login(@PathVariable("userName") String userName, @PathVariable("password") String password) { // Vi har nu skickat userName och password frn våran klient }

Om jag vill skicka ett objekt från klient till server

@RequestMapping(value = "/login", method = RequestMethod.GET) public void login(@RequestBody LoginForm loginForm) { // Vi har nu skickat objektet lognForm }

Där våran klass LoginForm finns på både klient och server.

Om jag vill ha en återkoppling från min server så skriver jag

@RequestMapping(value = "/login", method = RequestMethod.POST) @ResponseBody public String login() { return "Återkoppling"; }

Eller om jag skickar ett objekt

@RequestMapping(value = "/login", method = RequestMethod.GET) @ResponseBody public MitObjekt login() { return new MittObjekt(); }

Där klassen MittObjekt finns både hos server och klient.

Har jag rätt? Eller har jag fel? Finns det smartare sätt att skriva?

Skulle starkt rekommendera att du inte skickar användarnamn och lösenord som pathparams, är som gjort för en katastrof.

Låter som att du är ute efter något åt det här hållet
https://stackoverflow.com/questions/40226481/how-to-map-json-...
dvs skicka och ta emot JSON (som du kan mappa mot objekt).

t.ex.

@PostMapping(path = "/login", consumes = "application/json") public ResponseEntity<String> login(@RequestBody User user) { readUsername(user.username); readPassword(user.password); // eller vad du nu vill göra med din användare return new ResponseEntity.ok(user); }

Och så kan det du skickar se ut kanske såhär:

{ "username" : "username", "password" : "password" }

Kika även lite på de lite mordernare annoteringarna.
I det här fallet PostMapping istället för RequestMapping (method = RequestMethod.POST)

Nu vet jag inte vad du tänkte köra på klienten, med Typescript kan du mappa till objekt också.
Java:

public class User { private String username; private String password; User() {} // getter och setter

Typescript:

export class User { username: string; password: string; }

Notera att det är massor av säkerhetsaspekter som är bristande här, se det för vad det är, ett kort förslag på vart du kan börja.

Skriver du en inloggning på det här viset får du någon arg säkerhetsansvarig efter dig.

Visa signatur

Stationär: Core i9 13900k | Asus X790 ROG Strix Gaming-F | 32GB DDR5 | RX 7900 XT | Lian Li PC-O11 dynamic evo
Laptop: Macbook Air | Apple M1

Permalänk
Skrivet av MaxieTheHatter:

Skulle starkt rekommendera att du inte skickar användarnamn och lösenord som pathparams, är som gjort för en katastrof.

Låter som att du är ute efter något åt det här hållet
https://stackoverflow.com/questions/40226481/how-to-map-json-...
dvs skicka och ta emot JSON (som du kan mappa mot objekt).

t.ex.

@PostMapping(path = "/login", consumes = "application/json") public ResponseEntity<String> login(@RequestBody User user) { readUsername(user.username); readPassword(user.password); // eller vad du nu vill göra med din användare return new ResponseEntity.ok(user); }

Och så kan det du skickar se ut kanske såhär:

{ "username" : "username", "password" : "password" }

Men om jag skickar lösenord och användarnamn i form av objekt så går det väll ändå att läsa? Eller ska man satsa på att skicka krypterat lösenord?

Citat:

Kika även lite på de lite mordernare annoteringarna.
I det här fallet PostMapping istället för RequestMapping (method = RequestMethod.POST)

Modernare? Så PostMapping är mer modernare än RequestMapping ? Då använder jag PostMapping när jag vill skicka data, GetMapping när jag vill hämta data. Jag vet inte vad PutMapping, DeleteMapping samt PushMapping kommer till nytta. Förklara gärna. Finner inget behov av dom.

Citat:

Nu vet jag inte vad du tänkte köra på klienten, med Typescript kan du mappa till objekt också.
Java:

public class User { private String username; private String password; User() {} // getter och setter

Typescript:

export class User { username: string; password: string; }

Jag vill ha kommunikation mellan en databas och en annat javaprogram via REST.

Citat:

Notera att det är massor av säkerhetsaspekter som är bristande här, se det för vad det är, ett kort förslag på vart du kan börja.
Skriver du en inloggning på det här viset får du någon arg säkerhetsansvarig efter dig.

Jo, jag vet att det är bristande då jag aldrig har kommit igång med sådant förut. Jag uppskattar dina tips och det var att skicka lösenord och användarnamn i form av ett objekt istället?

Men borde inte spring-security lösa detta åt mig?

Permalänk
Medlem

Jag kan inte Spring och jag förstår inte exakt vad du vill göra. Men en kommentar kan jag nog ändå bistå med.

Du har konfigurerat användande av Basic Authentication. Det är ett OK val för ett REST-API, men bara om du kör över https, annars skickas lösenordet i princip i klartext. Eftersom du har skapat en login-controller så tror jag inte du vet hur Basic Authentication fungerar. HTTP Authorization-headern används av klienten för att skicka användarnamn:lösenord base64-kodat. Läs på specen om du tänker använda detta. RFC 7617.

Sedan kan jag inte se hur du kopplat din användardatabas med konfigurationen för Basic Authentication heller, men det kan vara min Spring-okunnighet som spelar in där. Över huuvud taget är det inte speciellt bra att ha lokala databaser för användare, det kommer bli en belastning förr eller senare. Men i ett hobby-projekt är det förstås OK.

Permalänk
Skrivet av KAD:

Jag kan inte Spring och jag förstår inte exakt vad du vill göra. Men en kommentar kan jag nog ändå bistå med.

Du har konfigurerat användande av Basic Authentication. Det är ett OK val för ett REST-API, men bara om du kör över https, annars skickas lösenordet i princip i klartext. Eftersom du har skapat en login-controller så tror jag inte du vet hur Basic Authentication fungerar. HTTP Authorization-headern används av klienten för att skicka användarnamn:lösenord base64-kodat. Läs på specen om du tänker använda detta. RFC 7617.

Det jag tänker göra är ett program där jag kan logga in, hämta användare i en databas, ge dom olika rättigheter samt ändra lösenord, ta bort användare med mera.

Dessa användare och lösenord ska då ha då tillgäng till en annan databas samt andra metoder i programmet.

Sammanfattning:
Det blir fyra databaser.
* Users (Sköts av admin)
* Authoreties ( Sköts av admin)
* Logs (Sköts av admin)
* Fields (Sköts av admin)

Dock så ges en tabell från Logs och Fields till varje användare som dem får fylla på med sin data igenom POST och GET.
Dessa tabeller innehåller bara konfigurationsparametrar som användaren sätter själv.

Citat:

Sedan kan jag inte se hur du kopplat din användardatabas med konfigurationen för Basic Authentication heller, men det kan vara min Spring-okunnighet som spelar in där. Över huuvud taget är det inte speciellt bra att ha lokala databaser för användare, det kommer bli en belastning förr eller senare. Men i ett hobby-projekt är det förstås OK.

Här laddar jag in databasen.

@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired private DataSource dataSource; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.jdbcAuthentication() .dataSource(dataSource) .passwordEncoder(passwordEncoder()); }

Där dataSource är en böna från

@Configuration @EnableTransactionManagement public class HibernateConfig { private static final String USERNAME = "admin"; private static final String PASSWORD = "password"; private static final String DRIVER = "com.mysql.cj.jdbc.Driver"; private static final String ADDRESS = "jdbc:mysql://localhost:3306"; private static final String DATABASE = "MyDatabase"; private DriverManagerDataSource dataSource; @Bean public DataSource dataSource() { dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(DRIVER); dataSource.setUsername(USERNAME); dataSource.setPassword(PASSWORD); dataSource.setUrl(ADDRESS + "/" + DATABASE + "?createDatabaseIfNotExist=true"); return dataSource; }

Permalänk
Medlem
Skrivet av heretic16:

Men om jag skickar lösenord och användarnamn i form av objekt så går det väll ändå att läsa? Eller ska man satsa på att skicka krypterat lösenord?

Absolut, du ska aldrig skicka lösenord okrypterat.

Citat:

Modernare? Så PostMapping är mer modernare än RequestMapping ? Då använder jag PostMapping när jag vill skicka data, GetMapping när jag vill hämta data. Jag vet inte vad PutMapping, DeleteMapping samt PushMapping kommer till nytta. Förklara gärna. Finner inget behov av dom.

Missade länken jag plockade fram.
https://www.baeldung.com/spring-new-requestmapping-shortcuts

Modernare som i att det introducerades i en nyare version av Spring och mer eller mindre har ersatt requestmapping (gör i stora drag samma sak, men är mer lättläst)

Citat:

Jo, jag vet att det är bristande då jag aldrig har kommit igång med sådant förut. Jag uppskattar dina tips och det var att skicka lösenord och användarnamn i form av ett objekt istället?

Men borde inte spring-security lösa detta åt mig?

Spring Security kommer inte att lösa något åt dig, men du får däremot tillgång till lite trevliga verktyg.

Kolla närmare på någon brett använd lösning, förslagsvid Oauth med JWT för autentisering.
https://oauth.net/2/jwt/

Visa signatur

Stationär: Core i9 13900k | Asus X790 ROG Strix Gaming-F | 32GB DDR5 | RX 7900 XT | Lian Li PC-O11 dynamic evo
Laptop: Macbook Air | Apple M1

Permalänk
Skrivet av MaxieTheHatter:

Absolut, du ska aldrig skicka lösenord okrypterat.

Missade länken jag plockade fram.
https://www.baeldung.com/spring-new-requestmapping-shortcuts

Modernare som i att det introducerades i en nyare version av Spring och mer eller mindre har ersatt requestmapping (gör i stora drag samma sak, men är mer lättläst)

Tack för tipsen. Jag tar till mig detta.

Citat:

Spring Security kommer inte att lösa något åt dig, men du får däremot tillgång till lite trevliga verktyg.

Men är det inte så att Spring Security hjälper mig att koppla databasen med alla användare till Spring Security samt att hjälper mig begränsa vem som får göra POST och GET inom vissa länkar?

Jag tänkte på denna kod.

@Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic() .and() .authorizeRequests() // Normal users .antMatchers(HttpMethod.GET, "/user/**").hasRole("USER") // Administrator .antMatchers(HttpMethod.GET, "/user/**").hasRole("ADMIN") .antMatchers(HttpMethod.POST, "/user/**").hasRole("ADMIN") .antMatchers(HttpMethod.PUT, "/user/**").hasRole("ADMIN") .antMatchers(HttpMethod.PATCH, "/user/**").hasRole("ADMIN") .antMatchers(HttpMethod.DELETE, "/user/**").hasRole("ADMIN") .and() .csrf().disable() .formLogin().disable(); }

Citat:

Kolla närmare på någon brett använd lösning, förslagsvid Oauth med JWT för autentisering.
https://oauth.net/2/jwt/

Jag har hört talas som något som heter Oauth2. Vad är detta? Kan detta hjälpa mig "logga in" med mer säkerhet?

Permalänk
Skrivet av heretic16:

Men om jag skickar lösenord och användarnamn i form av objekt så går det väll ändå att läsa? Eller ska man satsa på att skicka krypterat lösenord?

Modernare? Så PostMapping är mer modernare än RequestMapping ? Då använder jag PostMapping när jag vill skicka data, GetMapping när jag vill hämta data. Jag vet inte vad PutMapping, DeleteMapping samt PushMapping kommer till nytta. Förklara gärna. Finner inget behov av dom.

Jag vill ha kommunikation mellan en databas och en annat javaprogram via REST.

Jo, jag vet att det är bristande då jag aldrig har kommit igång med sådant förut. Jag uppskattar dina tips och det var att skicka lösenord och användarnamn i form av ett objekt istället?

Men borde inte spring-security lösa detta åt mig?

De olika mappningarna används för olika HTTP-verb, det vill säga så att du kan bygga upp ett REST API enklare.

OAuth2 är det bra att du läser mer ingående om innan du ger dig på att använda det i ditt system, läs till exempel https://stormpath.com/blog/beginners-guide-jwts-in-java. Har själv jobbat med det nyligen(dock inte i Java) och det är lite krångligare att sätta sig in i.

Ett bra generellt tips för att navigera dig är att titta på ramverkets källkod eller API-dokumentation för att se hur saker fungerar och vad som går att göra.

Permalänk
Skrivet av plattangen:

De olika mappningarna används för olika HTTP-verb, det vill säga så att du kan bygga upp ett REST API enklare.

OAuth2 är det bra att du läser mer ingående om innan du ger dig på att använda det i ditt system, läs till exempel https://stormpath.com/blog/beginners-guide-jwts-in-java. Har själv jobbat med det nyligen(dock inte i Java) och det är lite krångligare att sätta sig in i.

Ett bra generellt tips för att navigera dig är att titta på ramverkets källkod eller API-dokumentation för att se hur saker fungerar och vad som går att göra.

Jag läste på om OAuth2 nu. Jag tror inte jag behöver använda mig av extern databas t.ex. Googles för att logga in
Jag tror jag ska göra mitt eget istället Alltså dom där 4 databaserna jag talar om.

* Users
* Grants
* Logs
* Fields

Där varje användare får tillgång till att skapa egna tabeller i Logs och i Fields.

Permalänk
Medlem
Skrivet av MaxieTheHatter:

Absolut, du ska aldrig skicka lösenord okrypterat.

Missade länken jag plockade fram.
https://www.baeldung.com/spring-new-requestmapping-shortcuts

Modernare som i att det introducerades i en nyare version av Spring och mer eller mindre har ersatt requestmapping (gör i stora drag samma sak, men är mer lättläst)

Spring Security kommer inte att lösa något åt dig, men du får däremot tillgång till lite trevliga verktyg.

Kolla närmare på någon brett använd lösning, förslagsvid Oauth med JWT för autentisering.
https://oauth.net/2/jwt/

Hur rimligt är det att serva sidan endast på https://... och förlita sig på HTTPS-protokollet för kryptering av användardata, så som lösenord?

Visa signatur

Arbets- / Spelstation: Arch Linux - Ryzen 5 3600 - RX 7900 XT - 32G DDR4
Server: Arch Linux - Core i5-10400F - 16G DDR4

Permalänk

Hmm...Efter flera dagars nötande med Spring och jag har inte kommit någonstans.

Är verkligen Spring + Hibernate + Security + OAuth2 så svårt?
Alla exempel verkar otroligt komplexa och det finns sällan förklaringar till varför man gör si och så.

Permalänk
Medlem
Skrivet av KAD:

Jag kan inte Spring och jag förstår inte exakt vad du vill göra. Men en kommentar kan jag nog ändå bistå med.

Du har konfigurerat användande av Basic Authentication. Det är ett OK val för ett REST-API, men bara om du kör över https, annars skickas lösenordet i princip i klartext. Eftersom du har skapat en login-controller så tror jag inte du vet hur Basic Authentication fungerar. HTTP Authorization-headern används av klienten för att skicka användarnamn:lösenord base64-kodat. Läs på specen om du tänker använda detta. RFC 7617.

Sedan kan jag inte se hur du kopplat din användardatabas med konfigurationen för Basic Authentication heller, men det kan vara min Spring-okunnighet som spelar in där. Över huuvud taget är det inte speciellt bra att ha lokala databaser för användare, det kommer bli en belastning förr eller senare. Men i ett hobby-projekt är det förstås OK.

På vilket sätt är det säkrare att skicka credentials med base64?

Permalänk
Medlem
Skrivet av heretic16:

Hmm...Efter flera dagars nötande med Spring och jag har inte kommit någonstans.

Är verkligen Spring + Hibernate + Security + OAuth2 så svårt?
Alla exempel verkar otroligt komplexa och det finns sällan förklaringar till varför man gör si och så.

Därför att nätverkssäkerhet är ett komplext ämne. Det är sällan man kan göra en säkerhetslösning med typ en oneliner i koden, utan det krävs aningens mer.. Spring hjälper dig massor här, men det är ingen färdig lösning du får. Du får verktygen, men implementationen får du göra själv.

Visa signatur

WS: Mac Studio M1 Max | 32 GB | 1TB | Mac OS
WS: Intel i5 12600K | 64 GB DDR4 @3600 Mhz | 2x1TB nvme 2x1TB SSD SATA | Windows 11 & Manjaro Linux
Bärbar: Macbook Pro 14" | M1 Pro | 16GB RAM | 512GB SSD | Mac OS
Servrar: Intel i7 10700K | 64 GB DDR4 @3600Mhz | 3 TB SSD + 22TB HDD | Unraid |
4x Raspberry pi 4b 8Gb | Dietpi |