MS Lesson22: Spring Security Project
Step1: docker-compose.yaml fayli
services:
postgres:
image: postgres:17
container_name: security2
environment:
POSTGRES_DB: security2
POSTGRES_USER: security2
POSTGRES_PASSWORD: security2
ports:
- "5555:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- demo-network
networks:
demo-network:
driver: bridge
volumes:
postgres_data:
application.yaml fayli
server:
port: 9999
spring:
profiles:
include:
- db
application-db.yaml fayli
spring:
datasource:
url: jdbc:postgresql://${DB_HOSTNAME:localhost}:${DB_PORT:5555}/security2
username: security2
password: security2
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
Step2: Entity-leri yaratmaq
User entity
Role entity
Permission entity
Step3: UserRepository yaratmaq
package az.etibarli.step3.repository;
import az.etibarli.step3.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
Step4: ProjectSecurityConfig yaratmaq
package az.etibarli.step3.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class ProjectSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
ve UserController yaratmaq
package az.etibarli.step3.controller;
import az.etibarli.step3.entity.User;
import az.etibarli.step3.repository.UserRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public UserController(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
@PostMapping("/register")
public ResponseEntity<String> registerUser(@RequestBody User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
userRepository.save(user);
return ResponseEntity.ok("User is created");
}
}
Step5: SecurityFilterChain yaratmaq. Bu ona gore lazimdir ki bizim yaratdigimiz /register endpointini public etmek. Cunki bu shekilde accessimiz yoxdur
package az.etibarli.step3.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class ProjectSecurityConfig {
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(request -> request
.requestMatchers("/register").permitAll()
.anyRequest().authenticated());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
*** CSRF nedir? Cross-Site Request Forgery.
CSRF esasen cookie/session ile ishleyen tetbiqlerde vacibdir. Biz REST API ve JWT token esasli auth qurdugumuz ucun bu dersde CSRF-i disable edirik.
Step6. CustomUserDetailsService yaratmaq.
Spring Security login zamani istifadecini DB-den username ile tapa bilsin.
Spring Security ozu bizim UserRepository-ni tanimir. Ona gore ona demeliyik: "Username gelse, get DB-den hemin user-i tap".
UserDetailsService Spring Security-nin istifadecinin tapmaq ucun istifade etdiyi interface-dir. Biz bu interface-i implement edib deyirik ki, istifadeci DB-den username ile tapilmalidir.
Step7. User entity-ni UserDetails etmek.
CustomUserDetailsService UserDetails qaytarmalidir, lakin bizim UserRepository User qaytarir. Ona gore User class-imiz Spring Security-nin gozlediyi interface-i implement etmelidir.
package az.etibarli.step3.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.proxy.HibernateProxy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
@Getter
@Setter
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "users")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
@ToString.Exclude
private Set<Role> roles = new LinkedHashSet<>();
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Stream.concat(
roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())),
roles.stream()
.flatMap(role -> role.getPermissions().stream())
.map(permission -> new SimpleGrantedAuthority(permission.getName()))
)
.distinct()
.toList();
}
@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
if (thisEffectiveClass != oEffectiveClass) return false;
User user = (User) o;
return getId() != null && Objects.equals(getId(), user.getId());
}
@Override
public final int hashCode() {
Long id = getId();
return id != null ? id.hashCode() : System.identityHashCode(this);
}
}
* Spring Security bizim Role ve Permission class-larimizi ozu tanimir. O, yalniz GrantedAuthority anlayishini bilir. Biz de role ve permission-lari SimpleGrantedAuthority formatina ceviririk.
Step8. CustomAuthenticationProvider yaratmaq.
Login zamani gelen username/password melumatlarini yoxlamaq.
Biz artiq bunlari etmishik:
a) UserRepository ile user-i DB-den tapa bilerik
b) CustomUserDetailsService ile Spring Security-ye user-i nece tapacagini deyirik
c) User artiq UserDetails oldu
package az.etibarli.step3.security;
import jakarta.annotation.Nullable;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
@Override
public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (passwordEncoder.matches(password, userDetails.getPassword())) {
return new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
}
throw new BadCredentialsException("Invalid password!");
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
AuthenticationProvider login prosesinde username ve password-u yoxlayan hissedir. Biz username ile user-i DB-den tapiriq, sonra gelen plain password-u DB-deki encoded password ile passwordEncoder.matches() vasitesile muqayise edirik. BCrypt password-u geri decode etmirik. Sadece matches() deyir ki, bu plain password hemin hash-a uygundur ya yox.
Step9. Indi ise /login endpointi yaradaq
package az.etibarli.step3.controller;
import az.etibarli.step3.controller.dto.LoginRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class AuthController {
private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
@PostMapping("/auth/login")
public ResponseEntity<String> login(@RequestBody LoginRequest request) {
UserDetails user = userDetailsService.loadUserByUsername(request.username());
if (!passwordEncoder.matches(request.password(), user.getPassword())) {
throw new BadCredentialsException("Invalid username or password");
}
return ResponseEntity.ok("Login success");
}
}
package az.etibarli.step3.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class ProjectSecurityConfig {
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(request -> request
.requestMatchers("/register", "/auth/login").permitAll()
.anyRequest().authenticated());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Step10. Novbeti addim kimi login ugurlu olduqda JWT token yaratmaq
JWT bize ona gore lazimdir ki, login olunmush user-i her request-de yeniden teniya bilek, amma server terefde session saxlamaq mecburiyyetinde qalmayaq. Yeni server her user ucun ayrica session saxlamir.
Token client-de olur, server ise her request-de token-i verify edir.
JWT shifreleme deyil. JWT icindeki melumat oxuna biler. One gore token-in icine password, card number, secret data yazilmamalidir.
a) dependency-ler elave etmek
b) application-security.yaml icinde JWT config saxlamaq
c)
package az.etibarli.step3.security;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public final class JwtSigningSupport {
private JwtSigningSupport() {
}
public static SecretKey hmacSha256KeyFromSecret(String secret) {
return Keys.hmacShaKeyFor(sha256(secret));
}
private static byte[] sha256(String value) {
try {
return MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
}
Config faylindaki secret adli String-i JWT ucun lazim olan imza acarina cevirir. Bu klas secret-i goturur, duzgun formata salir ve JWT tokeni imzalamaq/yoxlamaq ucun hazir acar verir. JWT yaradilarken ve yoxlanilarken eyni acar lazimdir. Bu klas hemin acari hazirlayir.
* JWT imza nedir?
JWT 3 hisseden ibaret olur: header.payload.signature
hader - tokenin tipi ve alqoritm.
payload - username, authorities, expiration ve s.
signature - senin bu tokene qoydugun imza
Imza olmadan token sadece base64 textdir. Her kes payload-i deyishib yeni token yaza biler.
Server imza ile deyir: "Bu token menim secret acarimla yaradilib".
Sonra request gelende yeniden yoxlayir: "Bu imza helede duzgundurmu".
Bes o zaman bu klasin JwtSigningSupport rolu nedir?
Spring Boot-da JWT və BCrypt — Sadə Dildə İzah
Problem nədir?
HTTP yaddaşsızdır. Sən bir səhifədən digərinə keçəndə server səni unudur.
Köhnə üsul: Session — server hər istifadəçinin məlumatını özündə saxlayırdı. Milyonlarla istifadəçi olsa server yavaşlayır.
Yeni üsul: JWT — server heç nə saxlamır. İstifadəçinin məlumatları tokenin içindədir.
Hissə 1 — Token Yaranması (Login)
Addım 1 — İstifadəçi credentials daxil edir
username → parvin
password → parvin
Addım 2 — Server bazada username-ə görə istifadəçini tapır
Addım 3 — Bazadakı BCrypt hash-i götürür
$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
Addım 4 — Hash-dən iki şey extract edir
$10$ → 2¹⁰ = 1024 dəfə iterasiya olunacaq
N9qo8uLOickgx2ZMRZoMye → salt (22 simvol)
Addım 5 — Şifrə yoxlanır
İstifadəçinin daxil etdiyi password həmin salt ilə qarışdırılır və 1024 dəfə iterasiya olunur. Nəticədə 31 simvolluq hash alınır.
"parvin" + salt → 1024 dövr → 31 simvolluq hash
Addım 6 — 3 hissə birləşdirilir → 60 simvol
$2a$10$ (7) + salt (22) + hash (31) = 60 simvol
passwordEncoder.matches() ilə müqayisə edilir. Bu metod XOR əsasında işləyir — həmişə sona qədər gedir, heç bir simvolda dayanmır. Buna görə Timing Attack mümkün deyil.
✅ Eynidir → şifrə düzdür, davam et
❌ Fərqlidir → şifrə yanlışdır, 401 qaytar
⚠️
String.equals()işlədilmir — o birinci fərqli simvolda dayanır, haker vaxtı ölçüb şifrə haqqında məlumat əldə edə bilər.
Addım 7 — JWT üçün Header və Payload hazırlanır
Header:
{"alg": "HS256", "typ": "JWT"} → Base64URL ilə kodlanır
Payload:
{"id": 42, "username": "parvin", "role": "admin", "exp": 1735000000} → Base64URL ilə kodlanır
⚠️ Base64 şifrələmə deyil! Hər kəs geri çevirib oxuya bilər. Buraya şifrə, kart nömrəsi yazma!
Addım 8 — HMAC açarı (SigningKey) yaradılır
Secret key (String) SHA-256 ilə dəqiq 32 bayta çevrilir və HMAC açarı yaradılır.
"mySecretKey" → SHA-256 → 32 bayt → SigningKey
Niyə SHA-256? HMAC-SHA256 açarı mütləq 32 bayt olmalıdır. String isə istənilən uzunluqda ola bilər. SHA-256 nə versən həmişə 32 bayt qaytarır.
Addım 9 — İmza yaradılır
Header və Payload nöqtə ilə birləşdirilir, sonra SigningKey ilə HMAC-a verilir:
Header_Base64 + "." + Payload_Base64 → birləşdirilir
HMAC(birləşdirilmiş mətn, SigningKey) → Signature
Addım 10 — Final token formalaşır
Header_Base64 . Payload_Base64 . Signature
Addım 11 — İstifadəçiyə access token və refresh token verilir
Hissə 2 — Token Yoxlanması (Hər Sorğu)
Addım 1 — İstifadəçi access token ilə sorğu göndərir
Addım 2 — HMAC açarı (SigningKey) yaradılır
Secret key → SHA-256 → 32 bayt → SigningKey
Addım 3 — Gələn tokenin Header və Payload-ı götürülür, signature yenidən hesablanır
HMAC(Header_Base64 + "." + Payload_Base64, SigningKey) = HesablanmışSignature
Addım 4 — Müqayisə edilir
MessageDigest.isEqual() ilə müqayisə edilir. Bu metod da XOR əsasında işləyir — sabit vaxt, Timing Attack yoxdur.
✅ Eynidir → token etibarlıdır, access verilir
❌ Fərqlidir → token saxta, 401 qaytar
Hissə 3 — Secret Key Niyə Bu Qədər Vacibdir?
Əgər secret key açılsa — hər kəs öz tokenini özü yarada bilər:
1. Mövcud tokeni götür, Payload-ı decode et (Base64-dür, asandır)
{"role": "user"}
2. Payload-ı dəyişdir
{"role": "admin"}
3. Dəyişdirilmiş Payload-ı Base64 et
4. Secret key ilə SigningKey yarat
5. HMAC(Header_Base64 + "." + YeniPayload_Base64, SigningKey) → YeniSignature
6. Yeni token:
Header_Base64 . YeniPayload_Base64 . YeniSignature
7. Server yoxlayır → signature düzdür → admin kimi daxil olur ✅
Server fərqi görmür — çünki token texniki olaraq tamamilə düzgündür.
Secret key necə qorunmalıdır?
❌ Yanlış → jwt.secret = mySecretKey123 (koda yazılır, GitHub-a yüklənir)
✅ Düzgün → jwt.secret = ${JWT_SECRET} (mühit dəyişənindən oxunur)
Production-da isə Vault kimi xüsusi secret management alətləri işlədilir.
Hissə 4 — Timing Attack Nədir?
String.equals() müqayisəni birinci fərqli simvolda dayandırır:
"ABC" vs "XBC" → çox tez bitir (1-ci simvolda fərq)
"ABC" vs "ABX" → bir az gec bitir (3-cü simvolda fərq)
Haker minlərlə cəhd edib vaxtı ölçsə — hash haqqında ipucu əldə edər.
Buna görə həm BCrypt, həm JWT sabit vaxt alan müqayisə işlədir:
BCrypt → passwordEncoder.matches() → XOR, sabit vaxt
JWT → MessageDigest.isEqual() → XOR, sabit vaxt
Həmişə sona qədər gedir — fərq harada olursa olsun, vaxt dəyişmir.
Xülasə
| Mərhələ | Nə baş verir |
|---|---|
| Qeydiyyat | Şifrə BCrypt ilə hashlanır (salt + 1024 iterasiya), bazaya yazılır |
| Login | BCrypt.matches() ilə şifrə yoxlanır, JWT token yaradılır |
| Token yaranması | Header + Payload → Base64, Secret → SigningKey, HMAC → Signature |
| Hər sorğu | SigningKey ilə signature yenidən hesablanır, MessageDigest.isEqual() ilə müqayisə edilir |
| Təhlükəsizlik | Secret key açılsa sistem tam açılır — mühit dəyişənində saxla |
Step11. Indi ise JwtAuthenticationFilter yaradiriq ve ProjectSecurityConfig-i stateless session ve filter ile yenileyirik.
package az.etibarli.step3.filter;
import az.etibarli.step3.config.JwtProperties;
import az.etibarli.step3.security.JwtSigningSupport;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.crypto.SecretKey;
import java.io.IOException;
import java.util.Date;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final String BEARER_PREFIX = "Bearer ";
private final UserDetailsService userDetailsService;
private final SecretKey signingKey;
public JwtAuthenticationFilter(JwtProperties jwtProperties, UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
this.signingKey = JwtSigningSupport.hmacSha256KeyFromSecret(jwtProperties.getSecret());
}
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith(BEARER_PREFIX)) {
filterChain.doFilter(request, response);
return;
}
String token = header.substring(BEARER_PREFIX.length());
if (SecurityContextHolder.getContext().getAuthentication() != null) {
filterChain.doFilter(request, response);
return;
}
try {
Claims claims = Jwts.parser()
.verifyWith(signingKey)
.build()
.parseSignedClaims(token)
.getPayload();
if (claims.getExpiration().before(new Date())) {
filterChain.doFilter(request, response);
return;
}
String username = claims.getSubject();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (JwtException | IllegalArgumentException ignored) {
// invalid or malformed JWT — continue without authentication
}
filterChain.doFilter(request, response);
}
}
package az.etibarli.step3.config;
import az.etibarli.step3.filter.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class ProjectSecurityConfig {
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests(request -> request
.requestMatchers("/register", "/auth/login").permitAll()
.anyRequest().authenticated());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Step12. TestController yaradib test etmek
package az.etibarli.step3.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class TestController {
@GetMapping("/roles-user-manager-admin-read")
private ResponseEntity<String> one() {
return ResponseEntity.ok("roles user/manager/admin + read");
}
@GetMapping("/roles-manager-admin-read")
private ResponseEntity<String> two() {
return ResponseEntity.ok("roles manager/admin + read");
}
@GetMapping("/roles-manager-admin-write")
private ResponseEntity<String> three() {
return ResponseEntity.ok("roles manager/admin + write");
}
@GetMapping("/roles-admin-read")
private ResponseEntity<String> four() {
return ResponseEntity.ok("role admin + read");
}
@GetMapping("/roles-admin-write")
private ResponseEntity<String> five() {
return ResponseEntity.ok("role admin + write");
}
@GetMapping("/roles-admin-delete")
private ResponseEntity<String> six() {
return ResponseEntity.ok("role admin + delete");
}
}
Комментарии
Отправить комментарий