Asked Aug 26th, 3:31 p.m. 141 0 3
  • 141 0 3
0

Phân quyền trong Spring boot

Share
  • 141 0 3

Chào mọi người, em đang làm quen với spring boot. Hiện tại em đang ở phần phân quyền và em đang gặp vấn đề em phân quyền trong file SecurityConfig rồi nhưng sau khi có jwt token, em authorize thì api đó lại ra status 403 ạ. token đó của em chứa các thông tin ví dụ như sau : { "role": "ROLE_CUSTOMER", "sub": "trinh1233@gmail.com", "iat": 1724685317, "exp": 1724721317 }

Sau đây là các file của em, làm phiền anh chị xem qua xí em có sai ở đâu không ạ .

Đây là Account.java:

package com.example.bloomgift.model;

import java.time.LocalDateTime;
import java.util.Date;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;

@Entity
@Table(name="Account")
public class Account {
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "accountID") 
    private Integer accountID; 
    

    @Column(name = "email")
    private String email ; 

    @Column(name= "phone")
    private Integer phone ; 

    @Column(name = "address")
    private String address; 

    @Column(name= "fullname")
    private String fullname;

    @Column(name= "gender")
    private String gender ;
    
    @Column(name= "avatar")
    private String  avatar;

    @Column(name= "point")
    private Integer point; 
    
    @Column(name= "birthday")
    private Date birthday;

    @Column(name= "accountStatus")
    private Boolean accountStatus;

    @Column(name = "password")
    private String password; 


    @Column(name= "otp")
    private String otp ;

    @Column(name = "otp_generated_time")
    private LocalDateTime otp_generated_time;


    @ManyToOne
    @JoinColumn(name = "roleID")
    private Role roleID;

    public Account(){

    }
    public Account(Integer accountID, String email, Integer phone, String address, String fullname, String gender,
            String avatar, Integer point, Date birthday, Boolean accountStatus, String password, String otp,
            LocalDateTime otp_generated_time, Role roleID) {
        this.accountID = accountID;
        this.email = email;
        this.phone = phone;
        this.address = address;
        this.fullname = fullname;
        this.gender = gender;
        this.avatar = avatar;
        this.point = point;
        this.birthday = birthday;
        this.accountStatus = accountStatus;
        this.password = password;
        this.otp = otp;
        this.otp_generated_time = otp_generated_time;
        this.roleID = roleID;
    }



    public Integer getAccountID() {
        return accountID;
    }

    public void setAccountID(Integer accountID) {
        this.accountID = accountID;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getPhone() {
        return phone;
    }

    public void setPhone(Integer phone) {
        this.phone = phone;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getFullname() {
        return fullname;
    }

    public void setFullname(String fullname) {
        this.fullname = fullname;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getAvatar() {
        return avatar;
    }

    public void setAvatar(String avatar) {
        this.avatar = avatar;
    }

    public Integer getPoint() {
        return point;
    }

    public void setPoint(Integer point) {
        this.point = point;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Boolean getAccountStatus() {
        return accountStatus;
    }

    public void setAccountStatus(Boolean accountStatus) {
        this.accountStatus = accountStatus;
    }

    public String getOtp() {
        return otp;
    }

    public void setOtp(String otp) {
        this.otp = otp;
    }

    public LocalDateTime getOtp_generated_time() {
        return otp_generated_time;
    }

    public void setOtp_generated_time(LocalDateTime otp_generated_time) {
        this.otp_generated_time = otp_generated_time;
    }

    public Role getRoleID() {
        return roleID;
    }

    public void setRoleID(Role roleID) {
        this.roleID = roleID;
    }
    public String getPassword() {
        return password;
    }
   public void setPassword(String password) {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        this.password = passwordEncoder.encode(password);
    }
    
    public String getRoleName() {
        return roleID != null ? roleID.getRoleName() : null;
    }
   
}

Role.java:

package com.example.bloomgift.model;

import java.util.Set;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;

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

    @Column(name = "roleName")
    private String roleName ; 


    @OneToMany(mappedBy = "roleID")
    private Set<Account> accounts;

    public Role(){

    }

    public Role(Integer roleID, String roleName, Set<Account> accounts) {
        this.roleID = roleID;
        this.roleName = roleName;
        this.accounts = accounts;
    }

    public Integer getRoleID() {
        return roleID;
    }

    public void setRoleID(Integer roleID) {
        this.roleID = roleID;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public Set<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(Set<Account> accounts) {
        this.accounts = accounts;
    }

    
}

Hiện em đang có 2 roleName: 1. ROLE_ADMIN, 2. ROLE_CUSTOMER

JwtAuthenticationFilter.java:

package com.example.bloomgift.filter;

import java.io.IOException;
import java.util.Collections;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import com.example.bloomgift.service.AccountService;
import com.example.bloomgift.utils.JwtUtil;

import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private AccountService accountService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        final String authorizationHeader = request.getHeader("Authorization");

        String username = null;
        String jwt = null;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            username = jwtUtil.extractUsername(jwt);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.accountService.loadUserByUsername(username);

            if (jwtUtil.isTokenValid(jwt, userDetails)) {
                Claims claims = jwtUtil.extractAllClaims(jwt);
                String role = claims.get("role", String.class);

                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, Collections.singletonList(new SimpleGrantedAuthority(role)));
                usernamePasswordAuthenticationToken
                        .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        filterChain.doFilter(request, response);
    }
}

JwtUtil.java:


package com.example.bloomgift.utils;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;

@Service
public class JwtUtil {
    private String SECRET_KEY = "cacfewfewfewfew";

    public String generateToken(UserDetails userDetails, String role) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("role", role); // Add role to claims
        return generateToken(claims, userDetails);
    }

    public String generateToken(
            Map<String, Object> extractClaim,
            UserDetails userDetails) {
        return Jwts
                .builder()
                .setClaims(extractClaim)
                .setSubject(userDetails.getUsername().trim())
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
                .signWith(Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET_KEY)), SignatureAlgorithm.HS256)
                .compact();
    }

    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    public String extractRole(String token) {
        return extractClaim(token, claims -> claims.get("role", String.class)); // Extract role from claims
    }

    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    private Key getSignInKey() {
        byte[] keyBytes = Decoders.BASE64.decode(SECRET_KEY);
        return Keys.hmacShaKeyFor(keyBytes);
    }

    public Claims extractAllClaims(String token) {
        return Jwts
                .parserBuilder()
                .setSigningKey(getSignInKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    public Boolean isTokenValid(String token, UserDetails userDetails) {
        String username = extractUsername(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }

    public Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }
}

AccountService.java:

package com.example.bloomgift.service;

import java.util.Collections;
import java.util.Date;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.example.bloomgift.model.Account;
import com.example.bloomgift.reponse.AccountReponse;
import com.example.bloomgift.repository.AccountRepository;
import com.example.bloomgift.repository.RoleRepository;
import com.example.bloomgift.request.AccountRequest;
import com.example.bloomgift.utils.EmailUtil;
import com.example.bloomgift.utils.OtpUtil;


@Service
public class AccountService implements UserDetailsService {

    @Autowired
    private AccountRepository accountRepository;

    @Autowired 
    private RoleRepository roleRepository;

    
    @Autowired
    private EmailUtil emailUtil;

    @Autowired
    private OtpUtil otpUtil;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        Account account = accountRepository.findByEmail(email);

        if (account == null) {
            throw new UsernameNotFoundException("Email không tồn tại.");
        }

        GrantedAuthority authority = new SimpleGrantedAuthority(account.getRoleID().getRoleName());

        return new org.springframework.security.core.userdetails.User(
                account.getEmail(),
                account.getPassword(),
                Collections.singletonList(authority));
    }

SecurityConfig.java:

package com.example.bloomgift.config;

import java.util.Arrays;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import com.example.bloomgift.filter.JwtAuthenticationFilter;
import com.example.bloomgift.service.AccountService;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private final AccountService accountService;
    @Autowired
    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    @Autowired
    public SecurityConfig(AccountService accountService, JwtAuthenticationFilter jwtAuthenticationFilter) {
        this.accountService = accountService;
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    private static final String[] SWAGGER_URLS = {
            "/v2/api-docs",
            "/v3/api-docs",
            "/v3/api-docs/**",
            "/swagger-resources",
            "/swagger-resources/**",
            "/configuration/ui",
            "/configuration/security",
            "/swagger-ui/**",
            "/webjars/**",
            "/swagger-ui.html",
            "/api-docs/**",
    };

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable())
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                .authorizeHttpRequests(req -> req
                        .requestMatchers(SWAGGER_URLS).permitAll()
                        .requestMatchers("/api/auth/**").permitAll()
                        .requestMatchers("/oauth2/**").permitAll()
                        .anyRequest().authenticated())
                // .oauth2Login(oauth2 -> oauth2
                //         .loginPage("http://localhost:8080/oauth2/authorization/google")
                //         .defaultSuccessUrl("http://localhost:8080/api/auth/signInWithGoogle", true))
                // .formLogin(Customizer.withDefaults())
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

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

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000", "http://localhost:8080"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

Em rất cảm kích anh chị dành ít thời gian xem qua và cho em giải pháp ạ.

3 ANSWERS


Answered Sep 21st, 4:19 a.m.
Accepted
0

Chào bạn,

Haha, lỗi 403 quen thuộc rồi! Nhìn vào code của bạn, mình thấy có vẻ như bạn đang mắc lỗi nhỏ trong việc phân quyền, dẫn đến token có role nhưng Spring Security không xử lý đúng.

Giải pháp:

  1. Kiểm tra cách bạn định nghĩa quyền trong SecurityConfig: Kiểm tra lại phần authorizeHttpRequests. Hãy chắc chắn rằng bạn đang sử dụng hasAuthority() hoặc hasRole() một cách chính xác để map role trong token với các quyền truy cập của API.
    • Ví dụ, nếu API cần role "ROLE_CUSTOMER", hãy thêm:
    .requestMatchers("/api/customer").hasAuthority("ROLE_CUSTOMER") 
    
  2. Converter từ String sang GrantedAuthority: Bạn đang sử dụng SimpleGrantedAuthority(role) trong JwtAuthenticationFilter, nhưng bạn cần kiểm tra xem Spring Security có thể tự động chuyển đổi từ String "ROLE_CUSTOMER" thành GrantedAuthority chính xác không. Hãy xem xét việc tạo một CustomUserDetailsUserDetailsService để xử lý việc này rõ ràng hơn.

Gợi ý:

  • Sử dụng hasAuthority() hoặc hasRole() thay vì authenticated() khi bạn cần kiểm tra role.
  • Xem xét sử dụng Spring Security Expression Language (SpEL) để tăng tính linh hoạt trong việc phân quyền.

Kiểm tra cẩn thận lại các phần:

  • JwtAuthenticationFilter (đặc biệt là cách tạo UsernamePasswordAuthenticationToken).
  • SecurityConfig (phần authorizeHttpRequests).
  • AccountService (cách trả về UserDetails).

Hy vọng giải thích này giúp bạn tìm ra lỗi. Chúc bạn thành công!

Share
Answered Sep 6th, 6:47 a.m.
0

Theo mình thấy bạn có sử dụng user detail nhưng mà thực thể chính lưu trữ thông tin tài khoản của bạn dưới db lại là Account có chút sai sai ở đây, hàm genToken của bạn cũng khá là là, bạn có thể chạy debug, lúc decord jwt để thấy hẳn 1 luồng của bản thân mình hoặc thêm phân quyền cho chính api đó để check lại nhé

Share
Answered Sep 20th, 9:12 a.m.
0

Không sài lombok pro

Share
Viblo
Let's register a Viblo Account to get more interesting posts.