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.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Master Spring Security & JWT: Build Secure Authentication in Spring Boot

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_rsa

3. 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.

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.

javaSpring BootAuthenticationJWTAuthorizationspring-security
Su San Talks Tech
Written by

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.

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.