How to Secure a Spring Boot Application with Spring Security and JWT
This step‑by‑step guide shows how to integrate Spring Security into a Spring Boot project, configure Maven dependencies, define User, Role and Permission entities, set up MyBatis mappers, implement JWT generation and validation, and build the service, controller, and configuration layers for a complete authentication system.
0 Spring Security Overview
Spring Security is a powerful, highly customizable authentication and authorization framework for Java applications, widely used in medium‑to‑large projects to protect Spring‑based services.
1 Add Maven Dependencies
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<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-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- Persistence -->
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>2.2.3</version>
</dependency>2 Create Entity Classes
2.1 User Entity
package com.z.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
@Data
@TableName("user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
@ApiModelProperty(value = "id")
private Integer userId;
@TableField("user_role_id")
@ApiModelProperty(value = "用户角色")
private Integer userRoleId; // 1=用户 2=管理员
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ManyToOne
@JoinColumn(name = "user_role_id")
@TableField(exist = false)
private Role roles;
}2.2 Role Entity
package com.z.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
@Data
@TableName("role")
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
@ApiModelProperty(value = "角色id")
private Integer roleId;
@ApiModelProperty(value = "角色名称")
@Column(name = "role_name")
private String roleName;
@TableField(exist = false)
@ApiModelProperty(value = "角色权限列表")
private List<Permission> permissions;
}2.3 Permission Entity
package com.z.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@TableName("permission")
public class Permission implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
@ApiModelProperty(value = "权限id")
private Integer permissionId;
@ApiModelProperty(value = "权限资源")
private String permissionResources;
@ApiModelProperty(value = "权限名称")
private String permissionName;
}3 Create Data Access Layer (Mappers)
UserMapper
package com.z.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.z.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UserMapper extends BaseMapper<User> {
User findByUsername(String username);
User getRoleByUserId(Integer userId);
List<User> findUsersByName(@Param("name") String name);
}RoleMapper
package com.z.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.z.entity.Role;
import com.z.entity.Permission;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface RoleMapper extends BaseMapper<Role> {
Role findByUserId(Integer userId);
List<Permission> findPermissionsByRoleId(@Param("roleId") Integer roleId);
}4 JWT Implementation
4.1 JwtAuthenticationEntryPoint
package com.z.security;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
}
}4.2 JWT Properties (application.yml)
app:
jwt-secret: daf66e01593f61a15b857cf433aae03a005812b31234e149036bcc8dee755dbb
jwt-expiration-milliseconds: 604800000 # 7 days4.3 JwtTokenProvider
package com.z.security;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.slf4j.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
@Component
public class JwtTokenProvider {
private static final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);
@Value("${app.jwt-secret}")
private String jwtSecret;
@Value("${app.jwt-expiration-milliseconds}")
private long jwtExpirationDate;
public String generateToken(Authentication authentication) {
String username = authentication.getName();
Date now = new Date();
Date expiry = new Date(now.getTime() + jwtExpirationDate);
return Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiry)
.signWith(key())
.compact();
}
private Key key() {
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret));
}
public String getUsername(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(key())
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key()).build().parseClaimsJws(token);
return true;
} catch (ExpiredJwtException e) {
logger.error("The JWT token is expired: " + e.getMessage());
} catch (UnsupportedJwtException e) {
logger.error("Unsupported JWT type: " + e.getMessage());
} catch (MalformedJwtException e) {
logger.error("Malformed JWT token: " + e.getMessage());
} catch (SignatureException e) {
logger.error("Signature validation failed: " + e.getMessage());
} catch (IllegalArgumentException e) {
logger.error("Invalid JWT: " + e.getMessage());
}
return false;
}
}4.4 JwtAuthenticationFilter
package com.z.security;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider, UserDetailsService userDetailsService) {
this.jwtTokenProvider = jwtTokenProvider;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
String username = jwtTokenProvider.getUsername(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new org.springframework.security.web.authentication.WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearer = request.getHeader("Authorization");
if (StringUtils.hasText(bearer) && bearer.startsWith("Bearer ")) {
return bearer.substring(7);
}
return null;
}
}5 Service Layer
5.1 AuthService
package com.z.service;
import com.z.dto.UserLoginRequestDTO;
public interface AuthService {
String login(UserLoginRequestDTO loginDto);
}5.2 AuthServiceImpl
package com.z.service.impl;
import com.z.dto.UserLoginRequestDTO;
import com.z.mapper.UserMapper;
import com.z.security.JwtTokenProvider;
import com.z.service.AuthService;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
@Service
public class AuthServiceImpl implements AuthService {
private final AuthenticationManager authenticationManager;
private final UserMapper userMapper;
private final JwtTokenProvider jwtTokenProvider;
public AuthServiceImpl(JwtTokenProvider jwtTokenProvider, UserMapper userMapper,
AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
this.userMapper = userMapper;
this.jwtTokenProvider = jwtTokenProvider;
}
@Override
public String login(UserLoginRequestDTO loginDto) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
return jwtTokenProvider.generateToken(authentication);
}
}5.3 UserService
package com.z.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.z.entity.User;
public interface UserService extends IService<User> {
void registerUser(User user);
User findByUsername(String username);
}5.4 UserServiceImpl
package com.z.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.z.entity.User;
import com.z.mapper.UserMapper;
import com.z.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void registerUser(User user) {
String encrypted = passwordEncoder.encode(user.getPassword());
user.setPassword(encrypted);
baseMapper.insert(user);
}
@Override
public User findByUsername(String username) {
return baseMapper.findByUsername(username);
}
}6 Controller Layer
package com.z.controller;
import com.z.dto.*;
import com.z.entity.User;
import com.z.security.JwtTokenProvider;
import com.z.service.*;
import com.z.utils.ApiResult;
import lombok.AllArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.*;
@AllArgsConstructor
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthService authService;
private final UserService userService;
private final JwtTokenProvider jwtTokenProvider;
@PostMapping("/login")
public ApiResult authenticate(@RequestBody UserLoginRequestDTO loginDto) {
if (loginDto.getUsername() == null || loginDto.getUsername().isEmpty()) {
return ApiResult.error("用户名不能为空");
}
if (loginDto.getPassword() == null || loginDto.getPassword().isEmpty()) {
return ApiResult.error("密码不能为空");
}
String token = authService.login(loginDto);
JWTAuthResponse resp = new JWTAuthResponse();
String username = jwtTokenProvider.getUsername(token);
User user = userService.findByUsername(username);
if (user != null) {
resp.setAccessToken(token);
resp.setUserInfo(user);
return ApiResult.ok("登录成功", resp);
}
return ApiResult.unauthorized("用户未注册/用户名或密码错误");
}
@PostMapping("/logout")
public ApiResult logout(HttpServletRequest request, HttpServletResponse response) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
new SecurityContextLogoutHandler().logout(request, response, auth);
}
return ApiResult.ok("登出成功", auth);
}
@PostMapping("/register")
public ApiResult registerUser(@RequestBody UserRegisterRequestDTO dto) {
if (dto.getUsername() == null || dto.getUsername().isEmpty()) {
return ApiResult.error("用户名不能为空");
}
if (userService.findByUsername(dto.getUsername()) != null) {
return ApiResult.error("用户名已存在");
}
User user = new User();
user.setUsername(dto.getUsername());
user.setPassword(dto.getPassword());
user.setName(dto.getName());
userService.registerUser(user);
return ApiResult.ok("注册成功", user);
}
}7 Run the Application
package com.z;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}8 Test with Postman
After creating the MySQL database and the tables user, role, permission, and role_permission, you can use Postman to test the /api/auth/register, /api/auth/login, and /api/auth/logout endpoints. Successful responses include the JWT token and user information.
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.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.
