[Spring security] – Spring Boot Security JWT Authentication

Trong bài viết này mình sẽ hướng dẫn mọi người sử dụng Spring Boot JWT Authentication với các chức năng cơ bản: Người dùng có thể đăng ký tài khoản mới hoặc đăng nhập bằng username và password. Với vai trò của người dùng (quản trị viên, điều hành viên, người dùng), ứng dụng cho

Trong bài viết này mình sẽ hướng dẫn mọi người sử dụng Spring Boot JWT Authentication với các chức năng cơ bản:

  • Người dùng có thể đăng ký tài khoản mới hoặc đăng nhập bằng username và password.
  • Với vai trò của người dùng (quản trị viên, điều hành viên, người dùng), ứng dụng cho phép người dùng truy cập tài nguyên theo vai trò và quyền hạn.

Mọi người có thể tìm đọc các bài viết liên quan tại:

Đây là bản demo ứng dụng Spring Boot với cơ sở dữ liệu MySQL và thử nghiệm Rest Apis với Postman.

Methods	  Urls	                 Actions
POST	 /api/auth/signup	   signup new account
POST	 /api/auth/signin	   login an account
GET	     /api/test/all	       retrieve public content
GET	     /api/test/user	       access User’s content
GET	     /api/test/mod	       access Moderator’s content
GET	     /api/test/admin	   access Admin’s content

Spring Boot Signup và Login (JWT Authentication Flow)

Luồng xử lý người dùng đăng ký tài khoản mới, đăng nhập và xác thực.

Chuỗi token JWT phải được thêm vào Authorization Header ở mỗi HTTP Request nếu Client truy cập vào tài nguyên được bảo vệ. Trong trường hợp Token hết hạn Client sẽ cần Refresh Token:

Chi tiết sẽ có ở bài viết Spring Boot Security Refresh Token.

Spring Boot Server Architecture with Spring Security

  • WebSecurityConfigurerAdapter là class chốt chặn triển khai bảo mật. Nó cung cấp các cấu hình HttpSecurity để cấu hình cors, csrf, quản lý phiên, các quy tắc cho các tài nguyên cần được bảo vệ. Ta cũng có thể mở rộng và tùy chỉnh cấu hình mặc định có chứa các phần tử bên dưới.
  • UserDetailsService interface có một phương thức để lấy thông tin người dùng bằng username và trả về một đối tượng UserDetails mà Spring Security có thể sử dụng để xác thực và xác nhận.
  • UserDetails chứa các thông tin cần thiết (như: username, password, authorities – quyền hạn) để xây dựng một đối tượng Authentication.
  • Từ thông tin username và password trong request đăng nhập tạo ra instance của UsernamePasswordAuthenticationToken là implement của interface Authentication (để biết điều này phải đọc sâu vào trong code mới thấy được). Rồi AuthenticationManager sẽ sử dụng instance này để xác thực tài khoản đăng nhập.
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken
và
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer 
  • AuthenticationManager sử dụng DaoAuthenticationProvider cùng với UserDetailsService & PasswordEncoder validate instance của UsernamePasswordAuthenticationToken. Nếu thành công, AuthenticationManager trả về một đối tượng Authentication đầy đủ thông tin (bao gồm cả các quyền).
  • OncePerRequestFilter là Filter thực thi một lần duy nhất cho mỗi Request tới API. Nó cung cấp một phương thức doFilterInternal() – trong phương thức này ta sẽ triển khai: parse và validate chuỗi JWT, lấy thông tin người dùng (sử dụng UserDetailsService), kiểm tra Authorization.
  • AuthenticationEntryPoint là class handle các lỗi xác thực AuthenticationException

Repository chứa UserRepository & RoleRepository để làm việc với Database, sẽ được nhập vào Controller.

Controller nhận và xử lý yêu cầu sau khi nó được Filter bởi OncePerRequestFilter.

  • AuthController xử lý các yêu cầu đăng ký / đăng nhập
  • TestController xử lý các yêu cầu cần có quyền truy cập các phương thức được bảo vệ với các xác thực dựa trên vai trò (roles) và quyền hạn (permissions).

Package security

  • WebSecurityConfig extends WebSecurityConfigurerAdapter
  • UserDetailsServiceImpl implements UserDetailsService
  • UserDetailsImpl implements UserDetails
  • AuthenticationEntryPointHandler implements AuthenticationEntryPoint
  • AuthenticationFilter extends OncePerRequestFilter
  • JwtUtils cung cấp các phương thức generating, parsing, validating JWT

Model

Có 3 model chính là user, role, permission. Một người dùng có nhiều vai trò (bảng quan hệ user-role), một vai trò có nhiều quyền hạn (bảng quan hệ role-permission).

User

package tiendv.example.model;
// imports

@Entity
@Table(	name = "user",
        uniqueConstraints = {
                @UniqueConstraint(columnNames = "username"),
                @UniqueConstraint(columnNames = "email")
        })
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    @Size(max = 20)
    private String username;

    @NotBlank
    @Size(max = 50)
    @Email
    private String email;

    @NotBlank
    @Size(max = 120)
    private String password;

    @ManyToMany(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
    @JoinTable(	name = "user_role",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> roles = new HashSet<>();

    public User() {
    }

    public User(String username, String email, String password) {
        this.username = username;
        this.email = email;
        this.password = password;
    }

    // getter/setter
}

Role

package tiendv.example.model;
// imports

@Entity
@Table(name = "role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(length = 20)
    private String name;

    @ManyToMany(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
    @JoinTable(name = "role_permission", joinColumns = {@JoinColumn(name = "role_id")}, inverseJoinColumns = {@JoinColumn(name = "permission_id")})
    private Set<Permission> permissions = new HashSet<>();

    public Role() {
    }

    // getter/setter
}

Permission

package tiendv.example.model;
// imports

@Entity
@Table(name = "permission")
public class Permission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(length = 20)
    private String name;

    public Permission() {
    }

    public Permission(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    // getter/setter
}

Implement Repositories

UserRepository

package tiendv.example.repository;
// imports

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);

    Boolean existsByUsername(String username);

    Boolean existsByEmail(String email);
}

RoleRepository

package tiendv.example.repository;
import java.util.Optional;
// imports

@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
    Optional<Role> findByName(String name);
}

Configure Spring Security

WebSecurityConfig

package tiendv.example.security;
// imports

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
        // securedEnabled = true,
        // jsr250Enabled = true,
        prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserDetailsServiceImpl userDetailsService;

    @Autowired
    private AuthenticationEntryPointHandler unauthorizedHandler;

    @Bean
    public AuthenticationFilter authenticationJwtTokenFilter() {
        return new AuthenticationFilter();
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests().antMatchers("/api/auth/**").permitAll()
                .antMatchers("/api/test/**").permitAll()
                .anyRequest().authenticated();

        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

  • @EnableWebSecurity cho phép Spring tìm và tự động áp dụng class này là global Web security.
  • @EnableGlobalMethodSecurity cung cấp AOP security trên phương thức (Aspect Oriented Programming – phương pháp lập trình hướng khía cạnh). Cho phép inject 2 annotation là @PreAuthorize@PostAuthorize trên các phương cần bảo về quyền hạn truy cập. Tham khảo thêm tại đây
  • Việc override lại phương thức configure(HttpSecurity http) trong WebSecurityConfigurerAdapter interface, cho phép Spring security cấu hình CORS và CSRF khi chúng ta yêu cầu người dùng phải xác thực. Chúng ta cũng có thể thêm các class Filter và các clas Handle Exception(trong ví dụ này là AuthenticationFilter filter trước UsernamePasswordAuthenticationFilterAuthenticationEntryPoint để handler exception).
  • Spring security sẽ load thông tin chi tiết của người dùng để thực hiện xác thực và phân quyền (authentication và authorization). Vì vậy cần sử dụng UserDetailsService để triển khai.
  • Implementation của UserDetailsService sẽ được sử dụng để cấu hình DaoAuthenticationProvider bởi phương thức AuthenticationManagerBuilder.userDetailsService().
  • Chúng ta cũng cần PasswordEncoder cung cấp cho DaoAuthenticationProvider để thực hiện encode password. Nếu không, mặc định nó sẽ sử dụng kiểu plain text.

Implement UserDetails & UserDetailsService

Nếu như quá trình xác thực thành công, chúng ta có thể lấy được thông tin username, password, authorities của người dùng từ đối tượng Authentication.

Authentication authentication = 
        authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(username, password)
        );

UserDetails userDetails = (UserDetails) authentication.getPrincipal();
// userDetails.getUsername()
// userDetails.getPassword()
// userDetails.getAuthorities()

Nếu chúng ta muốn lưu nhiều thông tin hơn như id, email, phoneNumber, …, chúng ta có thể tạo một implementation của UserDetails interface.

UserDetailsImpl

package tiendv.example.security.service;
// imports

public class UserDetailsImpl implements UserDetails {
    private static final long serialVersionUID = 1L;

    private Long id;
    private String username;
    private String email;
    @JsonIgnore
    private String password;
    private Collection<? extends GrantedAuthority> authorities;

    public UserDetailsImpl(Long id, String username, String email, String password,
                           Collection<? extends GrantedAuthority> authorities) {
        this.id = id;
        this.username = username;
        this.email = email;
        this.password = password;
        this.authorities = authorities;
    }

    public static UserDetailsImpl build(User user) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        user.getRoles().forEach(r -> {
            authorities.add(new SimpleGrantedAuthority(r.getName()));
            r.getPermissions().forEach(p -> authorities.add(new SimpleGrantedAuthority(p.getName())));
        });

        return new UserDetailsImpl(
                user.getId(),
                user.getUsername(),
                user.getEmail(),
                user.getPassword(),
                authorities);
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    public Long getId() {
        return id;
    }

    public String getEmail() {
        return email;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        UserDetailsImpl user = (UserDetailsImpl) o;
        return Objects.equals(id, user.id);
    }
}

Như đã nói trước đó Spring security sử dụng UserDetailsService interface để load thông tin đối tượng UserDtails. Đây là code thư viện UserDetailsService interface của Spring security, nó chỉ chứa 1 phương thức duy nhất:

package org.springframework.security.core.userdetails;

public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

Bây giờ chúng ta sẽ implement và override phương thức loadUserByUsername() để trả về class UserDetailsImpl chứa đầy đủ thông tin hơn (đây là implementation của interface UserDetails, xem lại code bên trên).

UserDetailsServiceImpl

package tiendv.example.security.service;
// imports

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    UserRepository userRepository;

    /**
     * Override phương thức trong class UserDetailsService
     *
     * @param username
     * @return UserDetailsImpl là implements của UserDetails (UserDetails là đối tượng Spring security sử dụng để authen và authorize)
     * @throws UsernameNotFoundException
     */
    @Override
    @Transactional
    public UserDetailsImpl loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));

        return UserDetailsImpl.build(user);
    }
}

Trong đoạn code trên, chúng ta sử dụng UserRepositoty để lấy full thông tin của User, sau đó build đối tượng UserDetailsImpl (chính là UserDetails).

Filter Requests

Để định nghĩa ra một Filter (bộ lọc) thực thi trên mỗi request. Ta tạo ra một class filter kế thừa từ class OncePerRequestFilter và override phương thức doFilterInternal().

AuthenticationFilter

package tiendv.example.security.jwt;
// imports

public class AuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    private static final Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            String token = getTokenFromRequest(request);
            if (token != null && jwtUtils.validateJwtToken(token)) {
                String username = jwtUtils.getUserNameFromJwtToken(token);
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception e) {
            logger.error("Cannot set user authentication: {}", e);
        }

        filterChain.doFilter(request, response);
    }

    private String getTokenFromRequest(HttpServletRequest request) {
        String token = request.getHeader("Authorization");

        if (StringUtils.hasText(token) && token.startsWith("Bearer ")) {
            return token.substring(7, token.length());
        }

        return null;
    }
}

Trong phương thức doFilterInternal(), ví dụ mình đang xử lý như sau (mọi người có thể xử lý khác theo logic của mọi người):

  • Lấy JWT từ Authorization trong header của request (bỏ prefix ‘Bearer’)
  • Nếu request có JWT thì validate, parse, check hết hạn, lấy username trong JWT này
  • Từ thông tin username lấy ra thông tin UserDetails và tạo ra đối tượng Authentication
  • Sử dụng phương thức setAuthentication(authentication) set thông tin UserDetails vào SecurityContext. Kể từ sau đó, chúng ta có thể lấy thông tin UserDetails bất cứ khi nào thông qua SecurityContext như sau:
UserDetails userDetails =
	(UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

// userDetails.getUsername()
// userDetails.getPassword()
// userDetails.getAuthorities()
// hoặc các fiels khác định nghĩa trong UserDetailsImpl (là implementation của UserDetails)

JWT Utility

Đây là class support việc:

  • generate JWT từ username, date, expriation, secret,…
  • parser JWT, get username trong JWT
  • validate JWT

Nói chung là việc gen JWT, parse, validate… mọi người có thể suwrdungj thuật toán khác hay custom lại theo cách riêng của mọi người.

JwtUtils

package tiendv.example.security.jwt;
// import

@Component
public class JwtUtils {
    private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);

    @Value("${jwt.app.jwtSecret}")
    private String jwtSecret;

    @Value("${jwt.app.jwtExpirationMs}")
    private int jwtExpirationMs;

    public String generateJwtToken(UserDetailsImpl userDetails) {
        return Jwts.builder()
                .setSubject((userDetails.getUsername()))
                .setIssuedAt(new Date())
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + jwtExpirationMs);
    }

    public Claims getClaimsFromJwtToken(String token) {
        return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
    }

    private boolean isTokenExpired(Claims claims) {
        return claims.getExpiration().after(new Date());
    }

    public String getUserNameFromJwtToken(String token) {
        Claims claims = getClaimsFromJwtToken(token);
        if (claims != null && isTokenExpired(claims)) {
            return claims.getSubject();
        }
        return null;
    }

    public boolean validateJwtToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            logger.error("Invalid JWT signature: {}", e.getMessage());
        } catch (MalformedJwtException e) {
            logger.error("Invalid JWT token: {}", e.getMessage());
        } catch (ExpiredJwtException e) {
            logger.error("JWT token is expired: {}", e.getMessage());
        } catch (UnsupportedJwtException e) {
            logger.error("JWT token is unsupported: {}", e.getMessage());
        } catch (IllegalArgumentException e) {
            logger.error("JWT claims string is empty: {}", e.getMessage());
        }

        return false;
    }
}

Handle Authentication Exception

Cuối cùng là handle exception. Chúng ta tạo một class là AuthenticationEntryPointHandler implements interface AuthenticationEntryPoint. Sau đó override phương thức commence(). Phương thức này sẽ được trigger bất cứ khi nào request đến server không được xác thực hoặc xảy ra throw AuthenticationException

AuthenticationEntryPointHandler

package tiendv.example.security.jwt;
// imports

@Component
public class AuthenticationEntryPointHandler implements AuthenticationEntryPoint {

    private static final Logger logger = LoggerFactory.getLogger(AuthenticationEntryPointHandler.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        logger.error("Unauthorized error: {}", authException.getMessage());
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
    }
}

HttpServletResponse.SC_UNAUTHORIZED là lỗi 401. Nó chỉ ra rằng HTTP request không được xác thực.

Kịch bản

Request

  • LoginRequest: {username, password }
  • SignupRequest: {username, email, password }

Responses:

  • JwtResponse: {token, type, id, username, email, roles }
  • MessageResponse: {message }

RestAPIs Controllers

Controller cung cấp api đăng ký và đăng nhập

/api/auth/signup

  • Kiểm tra tồn tại username/email hay chưa
  • Tạo người dùng (User) mới (mặc định sẽ là ROLE_USER nếu không được chỉ định)
  • Lưu User vào database

/api/auth/signin

  • Xác thực người dùng {username, pasword }
  • update Authentication object trong SecurityContext
  • generate JWT
  • Lấy thông tin UserDetails từ Authentication object
  • Response thông tin JWT và thông tin UserDetails

AuthController

package tiendv.example.controller;
// imports

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/auth")
public class AuthController {
    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    UserRepository userRepository;

    @Autowired
    RoleRepository roleRepository;

    @Autowired
    PasswordEncoder encoder;

    @Autowired
    JwtUtils jwtUtils;

    @PostMapping("/signin")
    public ResponseEntity<?> login(@Valid @RequestBody LoginRequest loginRequest) {
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));

        SecurityContextHolder.getContext().setAuthentication(authentication);
        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
        String jwt = jwtUtils.generateJwtToken(userDetails);

        List<String> roles = userDetails.getAuthorities().stream()
                .map(item -> item.getAuthority())
                .collect(Collectors.toList());

        return ResponseEntity.ok(new JwtResponse(jwt,
                userDetails.getId(),
                userDetails.getUsername(),
                userDetails.getEmail(),
                roles));
    }

    @PostMapping("/signup")
    public ResponseEntity<?> register(@Valid @RequestBody SignupRequest signUpRequest) {
        if (userRepository.existsByUsername(signUpRequest.getUsername())) {
            return ResponseEntity
                    .badRequest()
                    .body(new MessageResponse("Error: Username is already taken!"));
        }

        if (userRepository.existsByEmail(signUpRequest.getEmail())) {
            return ResponseEntity
                    .badRequest()
                    .body(new MessageResponse("Error: Email is already in use!"));
        }

        User user = new User(signUpRequest.getUsername(), signUpRequest.getEmail(),
                            encoder.encode(signUpRequest.getPassword()));

        Set<String> asignRoles = signUpRequest.getRole();
        Set<Role> roles = new HashSet<>();

        // Nếu không truyền thì set role mặc định là ROLE_USER
        if (asignRoles == null) {
            Role userRole = roleRepository.findByName(RoleEnum.ROLE_USER.getRole())
                    .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
            roles.add(userRole);
        } else {
            asignRoles.forEach(role -> {
                switch (role) {
                    case "admin":
                        Role adminRole = roleRepository.findByName(RoleEnum.ROLE_ADMIN.getRole())
                                .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
                        roles.add(adminRole);

                        break;
                    case "mod":
                        Role modRole = roleRepository.findByName(RoleEnum.ROLE_MODERATOR.getRole())
                                .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
                        roles.add(modRole);

                        break;
                    default:
                        Role userRole = roleRepository.findByName(RoleEnum.ROLE_USER.getRole())
                                .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
                        roles.add(userRole);
                }
            });
        }

        user.setRoles(roles);
        userRepository.save(user);

        return ResponseEntity.ok(new MessageResponse("User registered successfully!"));
    }
}

Controller dùng để test Authorization

  • /api/test/all tất cả user đều có thể truy cập (kể cả chưa được phân quyền)
  • /api/test/user cho những user có ROLE_USER hoặc ROLE_MODERATOR hoặc ROLE_ADMIN
  • /api/test/mod cho những user có ROLE_MODERATOR
  • /api/test/admin cho nhưng user có ROLE_ADMIN

TestController

package tiendv.example.controller;
// imports

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/test")
public class TestController {
    @GetMapping("/all")
    public String allAccess() {
        return "Public Content.";
    }

    @GetMapping("/user")
    @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')")
    public String userAccess() {
        return "User Content.";
    }

    @GetMapping("/mod")
    @PreAuthorize("hasRole('MODERATOR')")
    public String moderatorAccess() {
        return "Moderator Board.";
    }

    @GetMapping("/admin")
    @PreAuthorize("hasRole('ADMIN')")
    public String adminAccess() {
        return "Admin Board.";
    }
}

Trong source code có file README.MD mình có để sẵn SQL tạo bảng, insert dữ liệu cần thiết. Chi tiết tại đây: Github

Run & Test

Mình sẽ tạo ra 3 user thông qua api /signup:

  • user admin với ROLE_ADMIN
  • user mod với ROLE_MODERATORROLE_USER, ROLE_A, ROLE_B, ROLE_C
  • user tiendv với ROLE_USER, ROLE_A, ROLE_B

/singup:

/signin với user dvtien (ROLE_USER, ROLE_A, ROLE_B, ROLE_C):

Sử dụng jwt của user dvtien truy cập resource /api/test/user:

Sử dụng jwt của user dvtien truy cập vào resource /api/test/all:

Sử dụng jwt của user dvtien truy cập vào resource /api/test/admin:

Sử dụng jwt của user dvtien truy cập vào resource /api/test/mod:

Không sử dụng jwt truy cập vào resource /api/test/user:

Tổng kết

Trên đây là hướng dẫn để mọi người hiểu hơn về cấu hình Spring security để xác thực và phân quyền người dùng.

Hy vọng mọi người sẽ hiểu được ý tưởng tổng thể của bài viết và áp dụng nó vào dự án của mọi người một cách thoải mái nhất.

Nguồn:https://thenewstack.wordpress.com/2021/11/24/spring-security-spring-boot-security-jwt-authentication/

Follow me: thenewstack.wordpress.com

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ