Integrating Spring Security with Spring Boot: Basic Authentication and Authorization

This tutorial walks through setting up Spring Security in a Spring Boot 2.7.x project, covering core concepts, project initialization, in‑memory and database authentication, URL‑based and annotation‑based access control, custom exception handling, and how to retrieve the current logged‑in user.

Java Tech Workshop
Java Tech Workshop
Java Tech Workshop
Integrating Spring Security with Spring Boot: Basic Authentication and Authorization

Prerequisites and Environment

Authentication verifies identity; Authorization decides what actions a user can perform. Spring Security is the official security framework for the Spring ecosystem, offering modularity, extensibility, and seamless Spring Boot integration.

Core concepts

Authentication : validates user credentials (e.g., username/password, SMS code).

Authorization : after authentication, controls access to endpoints based on roles/permissions.

Spring Security : built on Spring AOP and servlet filters, provides form login, logout, session management, CSRF protection, and permission control out of the box.

Development environment

JDK 8+

Spring Boot 2.7.x

Maven 3.6+

IDEA

Lombok

Project initialization and dependencies

Create Spring Boot project

In IntelliJ IDEA create a standard Spring Boot project named springboot-security-demo.

pom.xml core dependencies

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.15</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springboot-security-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- Lombok (optional) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-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>

Run project and observe default security

When the application starts Spring Boot prints a generated password, e.g. Using generated security password: .... The default username is user. Accessing http://localhost:8080 redirects to Spring Security’s default login page.

Spring Security core architecture

SecurityContextHolder : holds the current Authentication object.

Authentication : contains principal, credentials, authorities, and authentication status.

UserDetailsService : core interface for loading user data.

PasswordEncoder : enforces password hashing; BCrypt is the recommended implementation.

SecurityFilterChain : the chain of filters that processes every request.

@PreAuthorize : method‑level permission annotation.

In‑memory user authentication

Security configuration

package com.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

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

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails admin = User.builder()
                .username("admin")
                .password(passwordEncoder().encode("123456"))
                .roles("ADMIN")
                .build();

        UserDetails user = User.builder()
                .username("user")
                .password(passwordEncoder().encode("123456"))
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(admin, user);
    }
}

Explanation

@EnableWebSecurity

enables Spring Security web support. PasswordEncoder is mandatory; BCrypt adds a salt automatically. UserDetailsService provides user information; the in‑memory implementation stores the two users. .roles() assigns role names used later in authorization checks.

Test login

Restart the application and visit http://localhost:8080/login. Use admin / 123456 or user / 123456. After a successful login all endpoints are accessible because no URL restrictions have been defined yet.

URL‑based permission control

Define three categories of endpoints:

Public – accessible without authentication (e.g., home, login, static resources).

User – requires any authenticated user.

Admin – requires the ADMIN role.

HttpSecurity configuration (Spring Boot 2.7+ lambda style)

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // 1. Authorization rules
            .authorizeRequests()
                .antMatchers("/", "/home", "/login", "/css/**", "/js/**").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasRole("USER")
                .anyRequest().authenticated()
            .and()
            // 2. Form login
            .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/doLogin")
                .defaultSuccessUrl("/home")
                .failureUrl("/login?error=true")
                .permitAll()
            .and()
            // 3. Logout
            .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login?logout=true")
                .invalidateHttpSession(true)
                .clearAuthentication(true)
                .permitAll()
            .and()
            // 4. CSRF disabled for API‑centric projects (optional)
            .csrf().disable();

        return http.build();
    }
}

Rule explanation

permitAll()

– no authentication required. hasRole("ROLE_NAME") – requires the specified role. anyRequest().authenticated() – all other requests require authentication. formLogin() – enables form‑based login with auto‑generated endpoints. logout() – enables logout and session cleanup.

Test controller

package com.example.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @GetMapping("/home")
    public String home() {
        return "Home: accessible to everyone";
    }

    @GetMapping("/user/info")
    public String userInfo() {
        return "User info: accessible to USER role";
    }

    @GetMapping("/admin/info")
    public String adminInfo() {
        return "Admin info: accessible to ADMIN role";
    }

    @GetMapping("/test")
    public String test() {
        return "Test endpoint: accessible after login";
    }
}

Permission testing

Unauthenticated access to /admin/info, /user/info, /test redirects to the login page.

Login as user:

Can access /home, /user/info, /test.

Cannot access /admin/info (403).

Login as admin can access all endpoints.

Method‑level (annotation) permission control

Enable annotation security

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;

@EnableMethodSecurity(prePostEnabled = true)
@SpringBootApplication
public class SpringbootSecurityDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootSecurityDemoApplication.class, args);
    }
}

Common annotations

@PreAuthorize

– checks permissions before method execution (most common). @PostAuthorize – checks after method execution. @Secured – legacy role‑based annotation.

Example controller

package com.example.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AuthController {

    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/auth/admin")
    public String authAdmin() {
        return "Annotation permission: admin‑only endpoint";
    }

    @PreAuthorize("hasRole('USER')")
    @GetMapping("/auth/user")
    public String authUser() {
        return "Annotation permission: user‑only endpoint";
    }

    @PreAuthorize("hasAnyRole('ADMIN','USER')")
    @GetMapping("/auth/common")
    public String authCommon() {
        return "Annotation permission: accessible to any logged‑in user";
    }

    @PreAuthorize("hasAuthority('user:add')")
    @GetMapping("/auth/authority")
    public String authAuthority() {
        return "Annotation permission: requires 'user:add' authority";
    }
}

Database‑backed authentication

In‑memory users are suitable only for testing. Production systems should load users from a persistent store.

Dependencies (MyBatis‑Plus example)

<!-- MySQL driver -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- MyBatis‑Plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

Database schema

-- User table
CREATE TABLE `sys_user` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(50) NOT NULL COMMENT '用户名',
  `password` VARCHAR(100) NOT NULL COMMENT '密码(BCrypt加密)',
  `status` INT DEFAULT 1 COMMENT '状态 0-禁用 1-正常',
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Role table
CREATE TABLE `sys_role` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `role_name` VARCHAR(50) NOT NULL COMMENT '角色名称',
  `role_code` VARCHAR(50) NOT NULL COMMENT '角色编码 ADMIN/USER',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- User‑role association
CREATE TABLE `sys_user_role` (
  `user_id` BIGINT NOT NULL,
  `role_id` BIGINT NOT NULL,
  PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Test data (passwords are BCrypt‑hashed)
INSERT INTO sys_user (username, password, status) VALUES
('admin', '$2a$10$wJ5i4y9y9Q9m0pOe1x2L3u4S5D6F7G8H9J0K1L2M3N4B5V6C7X8Z9A', 1),
('user',  '$2a$10$wJ5i4y9y9Q9m0pOe1x2L3u4S5D6F7G8H9J0K1L2M3N4B5V6C7X8Z9A', 1);

INSERT INTO sys_role (role_name, role_code) VALUES
('管理员', 'ADMIN'),
('普通用户', 'USER');

INSERT INTO sys_user_role (user_id, role_id) VALUES
(1,1), (2,2);

Custom UserDetails implementation

package com.example.entity;

import lombok.Data;
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.List;
import java.util.stream.Collectors;

@Data
public class SecurityUser implements UserDetails {
    private Long id;
    private String username;
    private String password;
    private Integer status;
    private List<String> roles;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles.stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .collect(Collectors.toList());
    }

    @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 status == 1; }
}

Custom UserDetailsService implementation

package com.example.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.example.entity.SecurityUser;
import com.example.entity.SysUser;
import com.example.service.SysRoleService;
import com.example.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private SysUserService userService;

    @Autowired
    private SysRoleService roleService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1. Query user by username
        SysUser user = userService.getOne(Wrappers.<SysUser>lambdaQuery()
                .eq(SysUser::getUsername, username));
        if (user == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        // 2. Query roles
        List<String> roles = roleService.getRolesByUserId(user.getId());
        // 3. Wrap into SecurityUser
        SecurityUser securityUser = new SecurityUser();
        securityUser.setId(user.getId());
        securityUser.setUsername(user.getUsername());
        securityUser.setPassword(user.getPassword());
        securityUser.setStatus(user.getStatus());
        securityUser.setRoles(roles);
        return securityUser;
    }
}

Inject custom service into security configuration

package com.example.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private UserDetailsService userDetailsService;

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

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .userDetailsService(userDetailsService)
            .authorizeRequests()
                .antMatchers("/", "/home", "/login").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasRole("USER")
                .anyRequest().authenticated()
            .and()
            .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/doLogin")
                .defaultSuccessUrl("/home")
                .permitAll()
            .and()
            .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login")
                .permitAll()
            .and()
            .csrf().disable();

        return http.build();
    }
}

Retrieving the current logged‑in user

package com.example.controller;

import com.example.entity.SecurityUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserInfoController {

    @GetMapping("/getUserInfo")
    public String getUserInfo() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || !authentication.isAuthenticated()) {
            return "未登录";
        }
        Object principal = authentication.getPrincipal();
        if (principal instanceof UserDetails) {
            SecurityUser securityUser = (SecurityUser) principal;
            return "当前登录用户:" + securityUser.getUsername() + ",角色:" + securityUser.getRoles();
        }
        return "获取用户信息失败";
    }
}

Custom exception handling (401 & 403)

package com.example.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class SecurityExceptionConfig {

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return (request, response, authException) -> {
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            Map<String, Object> result = new HashMap<>();
            result.put("code", 401);
            result.put("msg", "未登录,请先登录");
            new ObjectMapper().writeValue(response.getWriter(), result);
        };
    }

    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return (request, response, accessDeniedException) -> {
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            Map<String, Object> result = new HashMap<>();
            result.put("code", 403);
            result.put("msg", "无权限访问该资源");
            new ObjectMapper().writeValue(response.getWriter(), result);
        };
    }
}

Inject the handlers in SecurityConfig:

http.exceptionHandling()
    .authenticationEntryPoint(authenticationEntryPoint)
    .accessDeniedHandler(accessDeniedHandler);

Key takeaways

Spring Security separates authentication (login) and authorization (permission).

Core interfaces: UserDetailsService for loading users and PasswordEncoder for password hashing (BCrypt recommended).

Two permission styles: URL‑based configuration and method‑level annotations.

Passwords must be stored hashed; plain text is rejected.

For API‑centric architectures CSRF can be disabled and token‑based authentication is preferred.

Custom exception handling provides consistent JSON responses for 401 and 403 errors.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavaspring-bootsecurityauthenticationauthorizationspring security
Java Tech Workshop
Written by

Java Tech Workshop

Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.