[[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