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.
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
@EnableWebSecurityenables 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
