Master Spring Security & JWT: Build Secure Authentication in Spring Boot
This tutorial explains how to integrate Spring Security with JWT in a Spring Boot application, covering authentication and authorization concepts, filter chain principles, project setup, dependency configuration, security configuration classes, custom token filters, and the complete request‑authentication flow.
Introduction
Authentication verifies a user's identity, while authorization assigns roles and permissions. Spring Security, built on the Spring framework, provides a comprehensive solution for both and can be easily extended for custom requirements.
Principles
Spring Security works as a chain of filter components that handle permission checks. The diagram below (green part configurable) shows the filter chain.
Practical Setup
Project Preparation
Use Spring Boot to integrate the security components.
1. Add the following dependencies to pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- Alibaba JSON parser -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.74</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>2. Configure application.yml:
spring:
application:
name: securityjwt
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/cheetah?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
password: 123456
server:
port: 8080
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.itcheetah.securityjwt.entity
configuration:
map-underscore-to-camel-case: true
rsa:
key:
pubKeyFile: C:\Users\Desktop\jwt\id_key_rsa.pub
priKeyFile: C:\Users\Desktop\jwt\id_key_rsa3. Create the required SQL tables (e.g., sys_user_info and product_info).
Dependency for Security and JWT
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Token generation and parsing -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>After adding these dependencies, the application starts and shows a default login page (username: user, password shown in the screenshot).
SecurityConfig Class
// Enable method security
@EnableGlobalMethodSecurity(prePostEnabled=true, securedEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// Authentication failure handler
@Autowired
private AuthenticationEntryPointImpl unauthorizedHandler;
// RSA key configuration
@Autowired
private RsaKeyProperties prop;
@Autowired
private UserInfoService userInfoService;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
httpSecurity.addFilter(new TokenLoginFilter(super.authenticationManager(), prop))
.addFilter(new TokenVerifyFilter(super.authenticationManager(), prop));
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userInfoService)
.passwordEncoder(passwordEncoder());
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}Key intercept rules include anyRequest, access, anonymous, denyAll, fullyAuthenticated, hasAnyAuthority, hasAnyRole, hasAuthority, hasIpAddress, hasRole, permitAll, and authenticated.
Authentication Failure Handler
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
int code = HttpStatus.UNAUTHORIZED;
String msg = "Authentication failed, cannot access system resources, please log in first";
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
}
}Authentication Flow
Custom Authentication Filter
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
private RsaKeyProperties prop;
public TokenLoginFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop) {
this.authenticationManager = authenticationManager;
this.prop = prop;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
UserPojo sysUser = new ObjectMapper().readValue(request.getInputStream(), UserPojo.class);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
return authenticationManager.authenticate(authRequest);
} catch (Exception e) {
try {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter out = response.getWriter();
Map resultMap = new HashMap();
resultMap.put("code", HttpServletResponse.SC_UNAUTHORIZED);
resultMap.put("msg", "Username or password incorrect!");
out.write(new ObjectMapper().writeValueAsString(resultMap));
out.flush();
out.close();
} catch (Exception outEx) { outEx.printStackTrace(); }
throw new RuntimeException(e);
}
}
@Override
public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
UserPojo user = new UserPojo();
user.setUsername(authResult.getName());
user.setRoles((List<RolePojo>)authResult.getAuthorities());
String token = JwtUtils.generateTokenExpireInMinutes(user, prop.getPrivateKey(), 24 * 60);
response.addHeader("Authorization", "Bearer " + token);
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter out = response.getWriter();
Map resultMap = new HashMap();
resultMap.put("code", HttpServletResponse.SC_OK);
resultMap.put("msg", "Authentication passed!");
resultMap.put("token", token);
out.write(new ObjectMapper().writeValueAsString(resultMap));
out.flush();
out.close();
}
}The default login URL is /login. When called, Spring Security invokes attemptAuthentication, which processes the credentials and generates a JWT token.
Custom Token Verification Filter
public class TokenVerifyFilter extends BasicAuthenticationFilter {
private RsaKeyProperties prop;
public TokenVerifyFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop) {
super(authenticationManager);
this.prop = prop;
}
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
chain.doFilter(request, response);
} else {
String token = header.replace("Bearer ", "");
Payload<UserPojo> payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), UserPojo.class);
UserPojo user = payload.getUserInfo();
if (user != null) {
UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
}
}
}Clients must include the JWT token in the Authorization header when accessing protected resources.
Supporting Classes
public interface UserInfoService extends UserDetailsService {}
@Service
@Transactional
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private SysUserInfoMapper userInfoMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserPojo user = userInfoMapper.queryByUserName(username);
return user;
}
}
@Data
public class UserPojo implements UserDetails {
private Integer id;
private String username;
private String password;
private Integer status;
private List<RolePojo> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> auth = new ArrayList<>();
auth.add(new SimpleGrantedAuthority("ADMIN"));
return auth;
}
@Override public String getPassword() { return this.password; }
@Override public String getUsername() { return this.username; }
@Override public boolean isAccountNonExpired() { return true; }
@Override public boolean isAccountNonLocked() { return true; }
@Override public boolean isCredentialsNonExpired() { return true; }
@Override public boolean isEnabled() { return true; }
}After successful authentication, the SecurityContext holds the Authentication object, and the generated token is returned to the client.
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.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
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.
