Tech Info

  1. Main page
  2. Backend
  3. Main content

Brings you to understand the usage of springboot3 + jwt + security

2023-10-07 1180hotness 0likes 0comments

Preface

Spring Security has become the first choice for permission verification in Java backends. Today I will take you through Security in depth by reading the code based on the open source project spring-boot-3-jwt-security. This article mainly explains Spring Security + JWT (Json Web Token) to implement user authentication and permission verification. All code is built on jdk17+. Let's get started!

Technology Introduction

  1. Springboot 3.0
  2. Spring Security
  3. Json Web Token (JWT)
  4. BCrypt
  5. Maven

Project Construction

  1. The project uses postgresql database to store user information and Token (why not Redis? Leave this hole for now), you can replace it with mysql database as you like
  2. Accessing the database uses jpa, which is quite convenient for some simple sql that can be automatically mapped based on method names. It doesn't matter if you haven't used it before. It won't affect reading today's article, and can be replaced with mybatis-plus etc later according to your actual needs
  3. This article uses Lombok to generate fixed template code
<parent>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-parent</artifactId>  
    <version>3.0.5</version>  
    <relativePath/> <!-- lookup parent from repository -->  
</parent>  
<groupId>com.alibou</groupId>  
<artifactId>security</artifactId>  
<version>0.0.1-SNAPSHOT</version>  
<name>security</name>  
<description>Demo project for Spring Boot</description>  
<properties>  
    <java.version>17</java.version>  
</properties>  
<dependencies>  
    <!-- jpa -->
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-data-jpa</artifactId>  
    </dependency>  
    <!-- spring security security framework -->
    <dependency>          
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-security</artifactId>  
    </dependency>  
    <!-- web dependency -->
    <dependency> 
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-web</artifactId>  
    </dependency>  
    <!-- database -->
    <dependency>  
        <groupId>org.postgresql</groupId>  
        <artifactId>postgresql</artifactId>  
        <scope>runtime</scope>  
    </dependency>
    <!-- lombok -->
    <dependency>  
        <groupId>org.projectlombok</groupId>  
        <artifactId>lombok</artifactId>  
        <optional>true</optional>  
    </dependency> 
    <!-- JWT -->
    <dependency>  
        <groupId>io.jsonwebtoken</groupId>  
        <artifactId>jjwt-api</artifactId>  
        <version>0.11.5</version>  
    </dependency>  
    <dependency>  
        <groupId>io.jsonwebtoken</groupId>  
        <artifactId>jjwt-impl</artifactId>  
        <version>0.11.5</version>  
    </dependency>  
    <dependency>  
        <groupId>io.jsonwebtoken</groupId>  
        <artifactId>jjwt-jackson</artifactId>  
        <version>0.11.5</version>  
    </dependency>

    <!-- doc remove this if not needed -->
    <dependency>  
        <groupId>org.springdoc</groupId>  
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>  
        <version>2.1.0</version>  
    </dependency>
    <!-- validation -->
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-validation</artifactId>  
    </dependency>  

    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-test</artifactId>  
        <scope>test</scope>  
    </dependency>  
    <dependency>  
        <groupId>org.springframework.security</groupId>  
        <artifactId>spring-security-test</artifactId>  
        <scope>test</scope>  
    </dependency>  
</dependencies>

<build>  
    <plugins>  
        <plugin>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-maven-plugin</artifactId>  
            <configuration>  
                <excludes>  
                    <exclude>  
                        <groupId>org.projectlombok</groupId>  
                        <artifactId>lombok</artifactId>  
                    </exclude>  
                </excludes>  
            </configuration>  
        </plugin>  
    </plugins>  
</build>

Project Configuration

Authentication Configuration

  1. When the project references the Security dependency, a random password will be generated when the project is started. We need to use this password to log in before we can use it. This will affect the normal use of many functions, such as the evil swagger. Let's take a detailed look at how to configure the paths we need authentication and the paths we need to release below.
@Configuration  
@EnableWebSecurity  
@RequiredArgsConstructor  
@EnableMethodSecurity  
public class SecurityConfiguration {

private final JwtAuthenticationFilter jwtAuthFilter;  
private final AuthenticationProvider authenticationProvider;  
private final LogoutHandler logoutHandler;

@Bean  
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {  
    http
    .csrf()
    .disable() //turn off csrf
    .authorizeHttpRequests()
    //configure paths to release
    .requestMatchers(
    "/api/v1/auth/**",
    "/v2/api-docs",
    "/v3/api-docs",
    "/v3/api-docs/**",
    "/swagger-resources",
    "/swagger-resources/**",
    "/configuration/ui",
    "/configuration/security",
    "/swagger-ui/**",
    "/webjars/**",
    "/swagger-ui.html"
    )
    .permitAll() //release all above paths


    /*
    * Permission verification (users need to have specified permissions to access)
    * requestMatchers: Specify paths to intercept
    * hasAnyAuthority: Specify required permissions
    */
    .requestMatchers("/api/v1/management/**").hasAnyRole(ADMIN.name(), MANAGER.name())
    .requestMatchers(GET, "/api/v1/management/**").hasAnyAuthority(ADMIN_READ.name(), MANAGER_READ.name())
    .requestMatchers(POST, "/api/v1/management/**").hasAnyAuthority(ADMIN_CREATE.name(), MANAGER_CREATE.name())
    .requestMatchers(PUT, "/api/v1/management/**").hasAnyAuthority(ADMIN_UPDATE.name(), MANAGER_UPDATE.name())
    .requestMatchers(DELETE, "/api/v1/management/**").hasAnyAuthority(ADMIN_DELETE.name(), MANAGER_DELETE.name())
    .anyRequest()
    .authenticated() //set all requests to be verified
    .and()
    .sessionManagement()
    .sessionCreationPolicy(SessionCreationPolicy.STATELESS) //use stateless Session
    .and()
    .authenticationProvider(authenticationProvider)
    //add jwt filter
    .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
    //set logout (will call logoutHandler's logout method when this path is called)
    .logout()
    .logoutUrl("/api/v1/auth/logout")
    .addLogoutHandler(logoutHandler)
    .logoutSuccessHandler((request, response,authentication) -> SecurityContextHolder.clearContext())
    ;

    return http.build();
    }
}
  1. The above code mainly implements four functions:
    • Release paths that do not require authentication (registration & login, swagger)
    • Configure permissions users need to access specific interfaces (e.g. must have permission to delete users to delete users)
    • Add preceding filter to determine if user is valid and get user permissions from Token: jwtAuthFilter
    • Configure logout Handler and listened path. The method in logoutHandler will be called automatically when accessing this path.

Login Configuration

We've talked about permissions and token verification above, now let's take a look at the logic of login. A UserDetails class is needed in security to define user account behavior. This is the key to user authentication. It mainly includes account, password, permissions, user status, etc. There are detailed comments in the code below

@Data  
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "_user")
public class User implements UserDetails {

  @Id
  @GeneratedValue
  private Integer id; //primary key ID
  private String firstname; //first name
  private String lastname; //last name
  private String email; //email
  private String password; //password

  /**
  * Role enumeration
  */
  @Enumerated(EnumType.STRING)
  private Role role;

  /**
  * Tokens associated with user
  * One-to-many mapping is used here with jpa
  */
  @OneToMany(mappedBy = "user")
  private List<Token> tokens;

  /**
  * Get user permissions
  * Get based on role enum permissions here (static instead of dynamically from database)
  * @return User permission list
  */
  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
      return role.getAuthorities();
  }

  /**
  * Get user password
  * Mainly used to specify your password field
  * @return User password
  */
  @Override
  public String getPassword() {
      return password;
  }

  /**
  * Get user account
  * Use email as account here
  * @return User account
  */
  @Override
  public String getUsername() {
      return email;
  }

  /**
  * Whether account is expired, the following methods are used to specify account status, 
  * since this is a demo project, all return true here
  * @return true not expired
  */
  @Override
  public boolean isAccountNonExpired() {
      return true;
  }

  /**
  * Whether account is unlocked
  * @return true not locked
  */
  @Override
  public boolean isAccountNonLocked() {
      return true;
  }

  /**
  * Whether password is expired
  * @return true not expired
  */
  @Override
  public boolean isCredentialsNonExpired() {
      return true;
  }

  /**
  * Whether account is activated
  * @return true activated
  */
  @Override
  public boolean isEnabled() {
      return true;
  }
}

After understanding the user entity, let's take a look at how login configuration is done and how security helps us manage user password verification. Let's take a look at the overall security configuration below

@Configuration
@RequiredArgsConstructor
public class ApplicationConfig {

  /**
  * Access user data table
  */
  private final UserRepository repository;

  /**
  * Get user detail Bean
  * Query whether user exists by email, throw user not found exception if not exists
  */
  @Bean
  public UserDetailsService userDetailsService() {
      //Call repository's findByEmail method to get user info, return if exists, otherwise throw exception
      return username -> repository.findByEmail(username)
      //Use Option's orElseThrow method here, return if exists, otherwise throw exception
      .orElseThrow(() -> new UsernameNotFoundException("User not found"));
  }

  /**
  * Authentication Bean
  * Pass in bean to get user info & password encoder
  * Refer back to AuthenticationProvider configuration in SecurityConfiguration, 
  * the Bean injected into the container here is used
  * This bean is mainly used for identity verification during user login, 
  * when we log in, security will help us call the authenticate method of this bean
  */
  @Bean
  public AuthenticationProvider authenticationProvider() {
      DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
      //Set bean to get user info
      authProvider.setUserDetailsService(userDetailsService());
      //Set password encoder
      authProvider.setPasswordEncoder(passwordEncoder());
      return authProvider;
  }

  /**
  * Authentication Manager
  */
  @Bean
  public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
      return config.getAuthenticationManager();
  }

  /**
  * Password Encoder
  * Mainly used to specify encryption method for storing passwords in database, 
  * to ensure passwords are not stored in plaintext
  * When security needs to verify passwords, it will encrypt the password passed in the request
  * then compare it with the password in the database
  */
  @Bean
  public PasswordEncoder passwordEncoder() {
      return new BCryptPasswordEncoder();
  }

}

The above code mainly does two things:

  • Specify how we get user information from the database based on user account
  • Specify password encoder for user passwordEncoder

You may be wondering now, how does security know which field in User entity is my account and which is my password? Do you still remember the UserDetails class, which is our User class? It has two methods getPassword & getUsername that return the account and password. There are a few other methods in User class that can be used to disable accounts etc according to actual business needs.

How Token is Generated

Token generation is mainly accomplished using toolkits. The user info & user permissions are mainly stored in the Token in this project. Let's first take a look at the code for the token toolkit, which mainly includes: generating token, getting info from token, and verifying token

@Service
public class JwtService {

  /**
  * Encrypt salt
  */
  @Value("${application.security.jwt.secret-key}")
  private String secretKey;

  /**
  * Token expiration time
  */
  @Value("${application.security.jwt.expiration}")
  private long jwtExpiration;

  /**
  * Token refresh time
  */
  @Value("${application.security.jwt.refresh-token.expiration}")
  private long refreshExpiration;

  /**
  * Get Username from Token
  * @param token Token
  * @return String
  */
  public String extractUsername(String token) {
      return extractClaim(token, Claims::getSubject);
  }

  /**
  * Get data from Token, return different data according to different Function passed in
  * eg: String extractUsername(String token)
  */
  public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
      final Claims claims = extractAllClaims(token);
      return claimsResolver.apply(claims);
  }

  /**
  * Generate Token without extra info
  */
  public String generateToken(UserDetails userDetails) {
      return generateToken(new HashMap<>(), userDetails);
  }

  /**
  * Generate Token, with extra info
  * @param extraClaims Extra data
  * @param userDetails User info
  * @return String
  */
  public String generateToken(
  Map<String, Object> extraClaims,
  UserDetails userDetails
  ) {
      return buildToken(extraClaims, userDetails, jwtExpiration);
  }

  /**
  * Generate refresh Token
  * @param userDetails User info
  * @return String
  */
  public String generateRefreshToken(
  UserDetails userDetails
  ) {
      return buildToken(new HashMap<>(), userDetails, refreshExpiration);
  }

  /**
  * Build Token method
  * @param extraClaims Extra info
  * @param userDetails //User info
  * @param expiration //Expiration time
  * @return String
  */
  private String buildToken(
      Map<String, Object> extraClaims,
      UserDetails userDetails,
      long expiration
      ) {
      return Jwts
      .builder()
      .setClaims(extraClaims) //body
      .setSubject(userDetails.getUsername()) //subject data
      .setIssuedAt(new Date(System.currentTimeMillis())) //set issue time
      .setExpiration(new Date(System.currentTimeMillis() + expiration)) //set expiration time
      .signWith(getSignInKey(), SignatureAlgorithm.HS256) //set digest algorithm
      .compact();
  }

  /**
  * Verify if Token is valid
  * @param token Token
  * @param userDetails User info
  * @return boolean
  */
  public boolean isTokenValid(String token, UserDetails userDetails) {
      final String username = extractUsername(token);
      return (username.equals(userDetails.getUsername())) && !isTokenExpired(token);
  }

  /**
  * Determine if Token is expired
  */
  private boolean isTokenExpired(String token) {
      return extractExpiration(token).before(new Date());
  }

  /**
  * Get expiration from Token
  */
  private Date extractExpiration(String token) {
      //generic method, pass in a Function, return a T
      return extractClaim(token, Claims::getExpiration);
  }

  /**
  * Get all data from Token
  */
  private Claims extractAllClaims(String token) {
      return Jwts
      .parserBuilder()
      .setSigningKey(getSignInKey())
      .build()
      .parseClaimsJws(token)
      .getBody();
  }

  /**
  * Get signIn Key
  * Used for Token encryption and decryption
  */
  private Key getSignInKey() {
      byte[] keyBytes = Decoders.BASE64.decode(secretKey);
      return Keys.hmacShaKeyFor(keyBytes);
  }
}

Registration and Login

We have looked through token generation, now let's get into the most critical part - user registration & user login.

  1. User registration: Receive user information passed in, generate user information (password will be encrypted by passwordEncoder) in database. After user information is saved successfully, an authentication token and a refreshToken will be created based on user info.

  2. User login: Get the account and password passed in by user, create a UsernamePasswordAuthenticationToken object. Then authenticate through authenticationManager's authenticate method, different exceptions will be thrown based on different errors

@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
public class AuthenticationController {

  private final AuthenticationService service;

  /**
  * Registration method
  * @param request Request body
  * @return ResponseEntity
  */
  @PostMapping("/register")
  public ResponseEntity<AuthenticationResponse> register(
  @RequestBody RegisterRequest request
  ) {
      return ResponseEntity.ok(service.register(request));
  }

  /**
  * Authentication (login method)
  * @param request Request body
  * @return ResponseEntity
  */
  @PostMapping("/authenticate")
  public ResponseEntity<AuthenticationResponse> authenticate(
  @RequestBody AuthenticationRequest request
  ) {
      return ResponseEntity.ok(service.authenticate(request));
  }

  /**
  * Refresh token
  * @param request Request
  * @param response Response
  * @throws IOException Exception
  */
  @PostMapping("/refresh-token")
  public void refreshToken(
  HttpServletRequest request,
  HttpServletResponse response
  ) throws IOException {
      service.refreshToken(request, response);
  }
}

You can see the methods in controller are calls to service methods. Let's look at the code in service now:

@Service
@RequiredArgsConstructor
public class AuthenticationService {

  private final UserRepository repository; //access user database
  private final TokenRepository tokenRepository; //access token database
  private final PasswordEncoder passwordEncoder; //password encoder
  private final JwtService jwtService; //JWT related methods
  private final AuthenticationManager authenticationManager; //Spring Security authentication manager

  /**
  * Registration method
  * @param request Request body
  * @return AuthenticationResponse (custom response structure)
  */
  public AuthenticationResponse register(RegisterRequest request) {
  //Construct user info
    var user = User.builder()
    .firstname(request.getFirstname())
    .lastname(request.getLastname())
    .email(request.getEmail())
    .password(passwordEncoder.encode(request.getPassword()))
    .role(request.getRole())
    .build();

  //Save user info to database
    var savedUser = repository.save(user);
  //Generate Token via JWT method
    var jwtToken = jwtService.generateToken(user);
  //Generate RefreshToken
    var refreshToken = jwtService.generateRefreshToken(user);
  //Save Token to database
    saveUserToken(savedUser, jwtToken);
  //Return response
    return AuthenticationResponse.builder()
    .accessToken(jwtToken) 
    .refreshToken(refreshToken)
    .build();
  }

  /**
  * Authentication (login) method
  * @param request Request body
  * @return AuthenticationResponse (custom response structure) 
  */
  public AuthenticationResponse authenticate(AuthenticationRequest request) {
    //Authenticate via Spring Security authentication manager
    //Exception will be thrown if authentication fails eg: BadCredentialsException for wrong password, UsernameNotFoundException for non-existing user
    authenticationManager.authenticate(
    new UsernamePasswordAuthenticationToken(
    request.getEmail(),
    request.getPassword()
    )
    );
    //Query user info by email, email is account in this project
    var user = repository.findByEmail(request.getEmail())
    .orElseThrow();
    //Generate Token via JWT method  
    var jwtToken = jwtService.generateToken(user);
    //Generate RefreshToken
    var refreshToken = jwtService.generateRefreshToken(user);
    //Set all previous tokens to invalid  
    revokeAllUserTokens(user);
    //Save new Token to database
    saveUserToken(user, jwtToken);
    //Package response
    return AuthenticationResponse.builder()
    .accessToken(jwtToken)
    .refreshToken(refreshToken)
    .build();
  }

  /**
  * Save user Token method
  * Save to database after constructing Token entity
  * @param user User info
  * @param jwtToken Token
  */
  private void saveUserToken(User user, String jwtToken) {
    var token = Token.builder()
    .user(user)
    .token(jwtToken)
    .tokenType(TokenType.BEARER) 
    .expired(false)
    .revoked(false)
    .build();
    tokenRepository.save(token);
  }

  /**
  * Set all user Tokens to invalid
  * @param user User info
  */
  private void revokeAllUserTokens(User user) {
    //Get all valid tokens for user
    var validUserTokens = tokenRepository.findAllValidTokenByUser(user.getId());
    if (validUserTokens.isEmpty()){
    return;
    }
    //If there are still valid tokens, set them to invalid
    validUserTokens.forEach(token -> {
    token.setExpired(true);
    token.setRevoked(true);
    });
    tokenRepository.saveAll(validUserTokens);
  }

  /**
  * Refresh token method
  * @param request Request 
  * @param response Response
  * @throws IOException Throw IO exception
  */
  public void refreshToken(
  HttpServletRequest request,
  HttpServletResponse response
  ) throws IOException {
    //Get authentication info from request header AUTHORIZATION
    final String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
    final String refreshToken;
    final String userEmail;
    //Directly return if auth info is empty or not starting with Bearer
    if (authHeader == null ||!authHeader.startsWith("Bearer ")) {
        return;
    }
    //Get RefreshToken from auth info
    refreshToken = authHeader.substring(7);
    //Get user info from RefreshToken
    userEmail = jwtService.extractUsername(refreshToken);
    if (userEmail != null) {
        //Query user by info, throw exception if user does not exist
        var user = this.repository.findByEmail(userEmail)
        .orElseThrow();

        //Verify if Token is valid
        if (jwtService.isTokenValid(refreshToken, user)) {
            //Generate new Token
            var accessToken = jwtService.generateToken(user);
            revokeAllUserTokens(user);
            saveUserToken(user, accessToken);
            //Generate new Token and RefreshToken and return via response
            var authResponse = AuthenticationResponse.builder()
            .accessToken(accessToken)
            .refreshToken(refreshToken)
            .build();
            new ObjectMapper().writeValue(response.getOutputStream(), authResponse);
        }
    }
  }
}

The above code mainly illustrates the process of returning token after registration & login. Since the validity period of token & refreshToken is quite long in the current project, the choice is made to save token to database (personal opinion!!!). Whether to save to redis can be decided based on actual business needs.

Request Filtering

Request filtering is mainly to dynamically parse token on each request to get user info and permissions, to ensure security of requested resources. Prevent unauthorized access etc.

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

  private final JwtService jwtService;
  private final UserDetailsService userDetailsService;
  private final TokenRepository tokenRepository;

  @Override
  protected void doFilterInternal(
  @NonNull HttpServletRequest request,
  @NonNull HttpServletResponse response,
  @NonNull FilterChain filterChain
  ) throws ServletException, IOException {
    //Determine if request is login request, if yes do not process
    if (request.getServletPath().contains("/api/v1/auth")) {
        filterChain.doFilter(request, response);
        return;
    }
    //Get authHeader from request header
    final String authHeader = request.getHeader("Authorization");
    final String jwt;
    final String userEmail;

    //If Token does not exist or does not start with Bearer, do not process
    if (authHeader == null ||!authHeader.startsWith("Bearer ")) {
        filterChain.doFilter(request, response);
        return;
    }
    //Extract Token info from authHeader
    jwt = authHeader.substring(7);
    //Get userEmail (account) from Token
    userEmail = jwtService.extractUsername(jwt);
    //Only process if Authentication in SecurityContextHolder is null
    if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
        //Get user info
        UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);

        //Query Token from database and determine if Token status is normal
        var isTokenValid = tokenRepository.findByToken(jwt)
            .map(t -> !t.isExpired() && !t.isRevoked())
            .orElse(false);

        //If Token is valid and Token status is normal, store user info in SecurityContextHolder
        if (jwtService.isTokenValid(jwt, userDetails) && isTokenValid) {
            UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
            userDetails, //user info
            null,
            userDetails.getAuthorities() //user permissions
            );
            authToken.setDetails(
                new WebAuthenticationDetailsSource().buildDetails(request) //access info
            );
            //Save user info and permissions to SecurityContextHolder context for later use
            //eg: get current user id, current user permissions etc
            SecurityContextHolder.getContext().setAuthentication(authToken);
        }
    }
    filterChain.doFilter(request, response);
  }
}

The main logic above is: Get token from request header. Verify validity of token and parse info in token to store in SecurityContextHolder context for later use.

Logout

We have talked about login and token verification, now just missing a logout. Do you still remember we configured a logout request path before: /api/v1/auth/logout. When we request this path, security will help us find the corresponding LogoutHandler, then call the logout method to implement logout.

@Service
@RequiredArgsConstructor
public class LogoutService implements LogoutHandler {

  private final TokenRepository tokenRepository;

  @Override
  public void logout(
  HttpServletRequest request,
  HttpServletResponse response,
  Authentication authentication
  ) {
    //Get authentication info from request header
    final String authHeader = request.getHeader("Authorization");
    final String jwt;
    if (authHeader == null ||!authHeader.startsWith("Bearer ")) {
      return;
    }
    //Extract token
    jwt = authHeader.substring(7);
    //Query token info from database
    var storedToken = tokenRepository.findByToken(jwt)
    .orElse(null);
    if (storedToken != null) {
        //Set token expired
        storedToken.setExpired(true);
        storedToken.setRevoked(true);
        tokenRepository.save(storedToken);
        //Clear SecurityContextHolder context
        SecurityContextHolder.clearContext();
    }
  }
}

security has done a lot for us, we just need to set the token to invalid and clear the SecurityContextHolder context to solve all problems.

Authentication

Below are some examples to explain two different authentication configuration methods

controller

@RestController
@RequestMapping("/api/v1/admin")
@PreAuthorize("hasRole('ADMIN')") //User needs ADMIN role to access
public class AdminController {

  @GetMapping
  @PreAuthorize("hasAuthority('admin:read')") //User needs admin:read permission to access
  public String get() {
      return "GET:: admin controller";
  }
  @PostMapping
  @PreAuthorize("hasAuthority('admin:create')") //User needs admin:create permission to access
  @Hidden
  public String post() {
      return "POST:: admin controller";
  }
  @PutMapping
  @PreAuthorize("hasAuthority('admin:update')")
  @Hidden
  public String put() {
      return "PUT:: admin controller";
  }
  @DeleteMapping
  @PreAuthorize("hasAuthority('admin:delete')")
  @Hidden
  public String delete() {
      return "DELETE:: admin controller";
  }
}

Configuration File

Below is part of the code for SecurityConfiguration configuration class:

image.png

Related

This article is licensed with Creative Commons Attribution 4.0 International License
Tag: Backend Security Spring Boot Spring Security
Last updated:2023-10-07

jimmychen

This person is a lazy dog and has left nothing

Like
< Last article
Next article >

Comments

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
Cancel

Archives
  • October 2023
  • September 2023
Categories
  • Algorithm
  • Android
  • Backend
  • Embedded
  • Security
Ads

COPYRIGHT © 2023 Tech Info. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang