Spring Boot security
1. Spring Security overview
Spring Security is Spring ecosystemin用于身份verification and authorization 强 big framework, providing了全面 securitysolution.
1.1 corefunctions
- 身份verification - verificationuser身份
- authorization - 控制user访问resource permission
- 攻击防护 - 防止common security攻击, such as CSRF, XSS etc.
- sessionmanagement - managementusersession
- 集成support - and OAuth2, JWT etc.标准集成
2. Spring Security Basicsconfiguration
2.1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.2 basicsecurityconfiguration
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.InMemoryUserDetailsmanagementr;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/login")
.permitAll()
)
.logout((logout) -> logout.permitAll());
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
UserDetails admin = User.withDefaultPasswordEncoder()
.username("admin")
.password("admin")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsmanagementr(user, admin);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
2.3 自定义login页面
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
@GetMapping("/login")
public String login() {
return "login";
}
}
<!-- src/main/resources/templates/login.html -->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<title>Login Page</title>
</head>
<body>
<div th:if="${param.error}">
Invalid username and password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
<form th:action="@{/login}" method="post">
<div>
<input type="text" name="username" placeholder="Username" />
</div>
<div>
<input type="password" name="password" placeholder="Password" />
</div>
<div>
<input type="submit" value="Sign In" />
</div>
</form>
</body>
</html>
3. 基于datalibrary authentication
in practicalapplicationin, 我们通常usingdatalibrarystoreuserinformation.
3.1 creationuser实体
package com.example.demo.entity;
import jakarta.persistence.*;
import lombok.Data;
@Entity
@Table(name = "users")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false, unique = true)
private String email;
private boolean enabled = true;
}
@Entity
@Table(name = "roles")
@Data
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String name;
}
@Entity
@Table(name = "user_roles")
@Data
public class UserRole {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
@ManyToOne
@JoinColumn(name = "role_id")
private Role role;
}
3.2 creation Repository
package com.example.demo.repository;
import com.example.demo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
Role findByName(String name);
}
@Repository
public interface UserRoleRepository extends JpaRepository<UserRole, Long> {
List<UserRole> findByUserId(Long userId);
}
3.3 implementation UserDetailsService
package com.example.demo.service;
import com.example.demo.entity.User;
import com.example.demo.entity.UserRole;
import com.example.demo.repository.UserRepository;
import com.example.demo.repository.UserRoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
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.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserRoleRepository userRoleRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found with username: " + username);
}
List<UserRole> userRoles = userRoleRepository.findByUserId(user.getId());
List<GrantedAuthority> authorities = new ArrayList<>();
for (UserRole userRole : userRoles) {
authorities.add(new SimpleGrantedAuthority(userRole.getRole().getName()));
}
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.isEnabled(),
true,
true,
true,
authorities
);
}
}
3.4 update Security configuration
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private CustomUserDetailsService userDetailsService;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/", "/home", "/register").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/login")
.permitAll()
)
.logout((logout) -> logout.permitAll());
return http.build();
}
@Bean
public Authenticationmanagementr authenticationmanagementr(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationmanagementr();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
4. JWT authentication
JSON Web Token (JWT) is a用于 in networkapplication间传递声明 开放标准, 常用于 REST API authentication.
4.1 添加依赖
<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>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
4.2 JWT toolclass
package com.example.demo.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
// from tokenin获取user名
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
// from tokenin获取过期时间
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
// from tokenin获取声明
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
// from tokenin获取所 has 声明
private Claims extractAllClaims(String token) {
return Jwts
.parserbuilder()
.setSigningKey(getSignKey())
.build()
.parseClaimsJws(token)
.getBody();
}
// checktoken is 否过期
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
// verificationtoken
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
// 生成token
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, username);
}
// creationtoken
private String createToken(Map<String, Object> claims, String username) {
return Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSignKey(), SignatureAlgorithm.HS256)
.compact();
}
// 获取signaturekey
private Key getSignKey() {
byte[] keyBytes = Decoders.BASE64.decode(secret);
return Keys.hmacShaKeyFor(keyBytes);
}
}
4.3 JWT filter器
package com.example.demo.security;
import com.example.demo.service.CustomUserDetailsService;
import com.example.demo.util.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
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 java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
String token = null;
String username = null;
if (authHeader != null && authHeader.startsWith("Bearer ")) {
token = authHeader.substring(7);
username = jwtUtil.extractUsername(token);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
4.4 JWT authenticationconfiguration
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private JwtAuthenticationFilter jwtFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.sessionmanagementment(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
// otherconfiguration...
}
4.5 authentication控制器
package com.example.demo.controller;
import com.example.demo.dto.AuthRequest;
import com.example.demo.dto.AuthResponse;
import com.example.demo.service.CustomUserDetailsService;
import com.example.demo.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.Authenticationmanagementr;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private Authenticationmanagementr authenticationmanagementr;
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public AuthResponse login(@RequestBody AuthRequest request) {
Authentication authentication = authenticationmanagementr.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
);
if (authentication.isAuthenticated()) {
String token = jwtUtil.generateToken(request.getUsername());
return new AuthResponse(token);
} else {
throw new UsernameNotFoundException("Invalid user request!");
}
}
}
5. OAuth2 authentication
OAuth2 is a开放标准, 允许userauthorization第三方application访问其resource, 而无需共享password.
5.1 OAuth2 客户端configuration
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
# application.properties
spring.security.oauth2.client.registration.google.client-id=your-client-id
spring.security.oauth2.client.registration.google.client-secret=your-client-secret
spring.security.oauth2.client.registration.google.scope=profile,email
spring.security.oauth2.client.registration.github.client-id=your-client-id
spring.security.oauth2.client.registration.github.client-secret=your-client-secret
spring.security.oauth2.client.registration.github.scope=read:user,user:email
5.2 OAuth2 securityconfiguration
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(withDefaults());
return http.build();
}
}
5.3 自定义 OAuth2 成功processing器
package com.example.demo.security;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessprocessingr;
import org.springframework.stereotype.Component;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class OAuth2Successprocessingr implements AuthenticationSuccessprocessingr {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// 自定义authentication成功逻辑
response.sendRedirect("/dashboard");
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private OAuth2Successprocessingr oauth2Successprocessingr;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.successprocessingr(oauth2Successprocessingr)
);
return http.build();
}
}
6. method级security
Spring Security 允许我们 in method级别applicationsecurity约束.
6.1 启用method级security
@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// configuration in 容
}
6.2 using @PreAuthorize and @PostAuthorize
package com.example.demo.service;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
@PreAuthorize("hasRole('USER')")
public List<Product> getAllProducts() {
// implementation逻辑
}
@PreAuthorize("hasRole('ADMIN')")
public Product addProduct(Product product) {
// implementation逻辑
}
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public Product updateProduct(Long id, Product product) {
// implementation逻辑
}
@PostAuthorize("returnObject.owner == authentication.name")
public Product getProductById(Long id) {
// implementation逻辑
}
}
7. securitybest practices
以 under is Spring Boot applicationsecurity 一些best practices:
7.1 authentication and authorization
- using强password哈希algorithms (such as BCrypt) storepassword
- 实施最 small permissionprinciples
- using HTTPS 保护传输data
- 定期update依赖library以修复security漏洞
7.2 防止common攻击
- 启用 CSRF 保护 ( for 于 has statusapplication)
- 防止 XSS 攻击, for user输入forverification and 转义
- 防止 SQL 注入, usingparameter化query
- configuration适当 CORS 策略
7.3 securityconfiguration
- 禁用不必要 functions and 端点
- configuration适当 security头部
- usingsecurity sessionmanagement
- 实施适当 log记录 and monitor
7.4 JWT best practices
- using强key并定期轮换
- 设置合理 过期时间
- 不要 in JWT instore敏感information
- implementationtoken刷 new mechanism