[[oktatas:web:back-end_framework:spring_boot|< Spring boot]]
====== Azonosítás ======
* **Szerző:** Sallai András
* Copyright (c) Sallai András, 2023
* Licenc: [[https://creativecommons.org/licenses/by-sa/4.0/|CC Attribution-Share Alike 4.0 International]]
* Web: https://szit.hu
===== Kezdő projekt =====
emp/
|-.mvn/
|-.vscode
|-src/
| |-main/
| | |-java/lan/zold/emp/
| | | |-EmpApplication.java
| | | |-Employee.java
| | | |-EmployeeControler.java
| | | |-EmployeeRepository.java
| | | |-User.java
| | | |-UserController.java
| | | `-UserRepository.java
| | `-resources/
| | |-static/
| | |-templates/
| | `-application.properties
| `-test/java/lan/zold/emp/
| `-EmpApplicationTests.java
|-target/
|-.gitignore
|-HELP.me
|-mvnw
|-mvnw.cmd
`-posm.xml
===== Token kezelése =====
Szükségünk van egy modellre User.java néven:
package lan.zold.emp;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Itt tároljuk a felhasználó azonosítóját, nevét és jelszavát.
Szükség van egy kapcsolatra az adatbázis tárolóval. Ez lesz a UserRepository.java:
package lan.zold.emp;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository {}
Jpa tárolót használunk.
Végül a három metódust készítünk:
* login()
* getToken()
* checkToken()
A login() metódust intézi a beléptetést. A sikeres belépés után generálunk
egy tokent. Ezt a getToken() metódus végzi.
A checkToken() metódust más kontrollerekből hívjuk, olyan útvonalaknál amit szeretnénk védeni.
package lan.zold.emp;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
@RestController
@RequestMapping("/api")
public class AuthController {
@Autowired
UserRepository userRepository;
@CrossOrigin
@PostMapping("/login")
public @ResponseBody String login(@RequestBody User user) {
//Ide jön felhasználó azonosítás
String token = getToken(user.getName());
return token;
}
public String getToken(String name) {
String key = "adsfffd";
Map claims = new HashMap<>();
String token = Jwts.builder()
.setSubject(name)
.setClaims(claims)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() * 500_000))
.signWith(SignatureAlgorithm.HS512, key.getBytes())
.compact();
return token;
}
public String checkToken(String token) {
try {
// A titkos kulcs, amivel a token alá lett írva
String key = "adsfffd";
// Token ellenőrzése és annak tartalmának kiolvasása
Claims claims = Jwts.parser().setSigningKey(key.getBytes()).parseClaimsJws(token).getBody();
// Token ellenőrzése, hogy lejárt-e
Date expirationDate = claims.getExpiration();
Date currentDate = new Date();
if (expirationDate.before(currentDate)) {
return "Lejárt token";
}
// Token érvényes
return "Érvényes token";
} catch (SignatureException e) {
// Hibás aláírás esetén
return "Hibás token";
} catch (Exception e) {
// Egyéb hibák esetén
return "Hiba történt: " + e.getMessage();
}
}
}
A felhasználó azonosítás itt még nincs kész, így tokent mindig megkapjuk.
===== Employee post védelme =====
A védelem azonban már működik. Az employees végpontot POST metódus esetén így csak az érvényes token elküldésével érhetjük el:
@CrossOrigin
@PostMapping(path="/employees")
public Employee store(
@RequestBody Employee emp,
@RequestHeader("Authorization") String tokenHeader) {
Employee res = null;
String token = tokenHeader.replace("Bearer ", "");
AuthController authController = new AuthController();
try {
String tokenOk = authController.checkToken(token);
if(tokenOk.equals("tokenok")) {
res = empRepository.save(emp);
}else {
String msg = "Hiba! A token nem megfelelő!";
throw new IllegalArgumentException(msg);
}
} catch (Exception e) {
System.err.println("Hiba! A token nem jó!");
}
return res;
}
===== Felhasználók kezelés =====
Fel kell tudnunk venni felhasználót:
package lan.zold.emp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
UserRepository userRepository;
@CrossOrigin
@PostMapping("/registry")
public User store(@RequestBody User user) {
User res = userRepository.save(user);
return res;
}
}
Most alakítsuk át az AuthController.java fájlban a login() metódust:
@CrossOrigin
@PostMapping("/login")
public @ResponseBody String login(@RequestBody User user) {
var users = userRepository.findAll();
var storedUser = users.stream()
.filter( userd -> userd.getName()
.equalsIgnoreCase( userd.getName() ) )
.findFirst()
.get();
String reqName = user.getName();
String reqPassword = user.getPassword();
if(storedUser.getName().equals(reqName) &&
storedUser.getPassword().equals(reqPassword)) {
String token = getToken(user.getName());
return token;
}
return "Hiba! Sikeretlen";
}
Vegyünk fel egy felhasználót:
http post http://localhost:8080/api/registry name='valaki' password='titok'
Lépjünk be:
http post http://localhost:8080/api/login name='valaki' password='titok'
===== Jelszavak titkosítása =====
A jelszavak titkosításához a Sping Security titkosítóját használjuk,
de nem szeretnék a teljes komponensre beállítani a Sprint Security-t.
Ezért az EmpApplication.java fájlban, a @SpringBootApplication annotációt
egészítsük ki:
package lan.zold.emp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
@SpringBootApplication(exclude = { SecurityAutoConfiguration .class })
public class EmpApplication {
public static void main(String[] args) {
SpringApplication.run(EmpApplication.class, args);
}
}
Most már felvehetjük a Sprint Security-t függőségnek:
org.springframework.boot
spring-boot-starter-security
Ez után vegyünk fel egy új szolgáltatást PasswordService néven.
package lan.zold.emp;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class PasswordService {
public String enPass(String clearPass) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String enPass = encoder.encode(clearPass);
return enPass;
}
}
Most már használhatjuk a UserController.java fájlban:
package lan.zold.emp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
UserRepository userRepository;
@Autowired
PasswordService passwordService;
@CrossOrigin
@PostMapping("/registry")
@ResponseStatus(code = HttpStatus.CREATED)
public User store(@RequestBody User user) {
String enpass = passwordService.enPass(user.getPassword());
user.setPassword(enpass);
User res = userRepository.save(user);
return res;
}
}
===== Titkosított jelszó ellenőrzése =====
A PasswordService.java fájlban vegyünk fel egy checkPass() metódust.
public boolean checkPass(String clearPass, String enPass) {
return encoder.matches(clearPass, enPass);
}
Az AuthController.java fájlban is titkosított jelszót kell ellenőrizünk.
@CrossOrigin
@PostMapping("/login")
public @ResponseBody String login(@RequestBody User user) {
var users = userRepository.findAll();
var storedUser = users.stream()
.filter( userd -> userd.getName()
.equalsIgnoreCase( userd.getName() ) )
.findFirst()
.get();
String reqName = user.getName();
boolean passOk = passwordService.checkPass(user.getPassword(), storedUser.getPassword());
if(storedUser.getName().equals(reqName) && passOk) {
String token = getToken(user.getName());
return token;
}
return "Hiba! Sikeretlen";
}
===== HTTPie teszt =====
Vegyünk fel egy felhasználót:
http post http://localhost:8080/api/registry
name='mari' password='titok'
Lépjünk be a felhasználóval:
http post http://localhost:8080/api/login
name='mari' password='titok'
Védett útvonalak tesztje:
http post http://localhost:8080/api/employees
name='mari' city='Pécs' salary=398
-A bearer -a eyJhbGc...
A -a és a -A kapcsolók vagy a legvégén vagy http parancs után közvetlenül jönnek.
===== GitHub =====
* https://github.com/oktat/empsb.git