Master Spring Security with JWT: Build a Secure Java Backend
This tutorial walks through setting up Spring Security with JWT in a Spring Boot project, covering environment configuration, Maven dependencies, custom authentication handlers, permission evaluation, JWT filtering, and testing, providing complete code examples and explanations for building a robust backend authentication system.
Preface
Spring Security is a security framework for Java enterprise applications, focusing on user authentication and authorization. Compared with Shiro, Security offers more powerful features but has a higher learning curve; the choice should depend on project requirements.
JWT is a token standard for securely transmitting information, especially suitable for distributed single sign‑on scenarios because the server does not need to store authentication data, and token validation is performed directly.
Project Environment
SpringBoot version: 2.1.6
SpringSecurity version: 5.1.5
MyBatis-Plus version: 3.1.0
JDK version: 1.8
Database password is encrypted; all passwords are set to 123456.
Maven Dependencies
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Security dependency -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- MyBatis‑Plus core library -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<!-- Alibaba Druid connection pool -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<!-- Apache Commons Lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<!-- FastJSON -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.45</version>
</dependency>
<!-- JWT dependencies -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
</dependencies>Configuration
# Configure server port
server:
port: 8764
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sans_security?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
jwt:
secret: JWTSecret
tokenHeader: Authorization
tokenPrefix: Sans-
expiration: 86400
antMatchers: /index/**,/login/**,/favicon.ico
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
global-config:
db-config:
id-type: AUTO
field-strategy: NOT_EMPTY
db-type: MYSQL
configuration:
map-underscore-to-camel-case: true
call-setters-on-nulls: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImplBase Classes
Entity, DAO, Service and related Spring Security user classes are omitted for brevity; refer to the source code repository.
JWT Utility Class
/**
* JWT utility class
*/
@Slf4j
public class JWTTokenUtil {
/**
* Generate access token
*/
public static String createAccessToken(SelfUserEntity selfUserEntity) {
String token = Jwts.builder()
.setId(selfUserEntity.getUserId() + "")
.setSubject(selfUserEntity.getUsername())
.setIssuedAt(new Date())
.setIssuer("sans")
.claim("authorities", JSON.toJSONString(selfUserEntity.getAuthorities()))
.setExpiration(new Date(System.currentTimeMillis() + JWTConfig.expiration))
.signWith(SignatureAlgorithm.HS512, JWTConfig.secret)
.compact();
return token;
}
}Exception Handlers
Classes for handling unauthorized access, unauthenticated requests, login failures, login successes, and logout successes are provided, each returning JSON responses with appropriate HTTP status codes.
Custom Authentication Provider
public class UserAuthenticationProvider implements AuthenticationProvider {
@Autowired
private SelfUserDetailsService selfUserDetailsService;
@Autowired
private SysUserService sysUserService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
SelfUserEntity userInfo = selfUserDetailsService.loadUserByUsername(userName);
if (userInfo == null) {
throw new UsernameNotFoundException("用户名不存在");
}
if (!new BCryptPasswordEncoder().matches(password, userInfo.getPassword())) {
throw new BadCredentialsException("密码不正确");
}
if (userInfo.getStatus().equals("PROHIBIT")) {
throw new LockedException("该用户已被冻结");
}
Set<GrantedAuthority> authorities = new HashSet<>();
List<SysRoleEntity> sysRoleEntityList = sysUserService.selectSysRoleByUserId(userInfo.getUserId());
for (SysRoleEntity sysRoleEntity : sysRoleEntityList) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + sysRoleEntity.getRoleName()));
}
userInfo.setAuthorities(authorities);
return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}Permission Evaluator
public class UserPermissionEvaluator implements PermissionEvaluator {
@Autowired
private SysUserService sysUserService;
@Override
public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
SelfUserEntity selfUserEntity = (SelfUserEntity) authentication.getPrincipal();
Set<String> permissions = new HashSet<>();
List<SysMenuEntity> sysMenuEntityList = sysUserService.selectSysMenuByUserId(selfUserEntity.getUserId());
for (SysMenuEntity sysMenuEntity : sysMenuEntityList) {
permissions.add(sysMenuEntity.getPermission());
}
return permissions.contains(permission.toString());
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
return false;
}
}Core Security Configuration
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserLoginSuccessHandler userLoginSuccessHandler;
@Autowired
private UserLoginFailureHandler userLoginFailureHandler;
@Autowired
private UserLogoutSuccessHandler userLogoutSuccessHandler;
@Autowired
private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;
@Autowired
private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;
@Autowired
private UserAuthenticationProvider userAuthenticationProvider;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler() {
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(new UserPermissionEvaluator());
return handler;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(userAuthenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(JWTConfig.antMatchers.split(",")).permitAll()
.anyRequest().authenticated()
.and()
.httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler)
.and()
.formLogin()
.loginProcessingUrl("/login/userLogin")
.successHandler(userLoginSuccessHandler)
.failureHandler(userLoginFailureHandler)
.and()
.logout()
.logoutUrl("/login/userLogout")
.logoutSuccessHandler(userLogoutSuccessHandler)
.and()
.exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler)
.and()
.cors()
.and()
.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.headers().cacheControl();
http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager()));
}
}JWT Authentication Filter
public class JWTAuthenticationTokenFilter extends BasicAuthenticationFilter {
public JWTAuthenticationTokenFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String tokenHeader = request.getHeader(JWTConfig.tokenHeader);
if (tokenHeader != null && tokenHeader.startsWith(JWTConfig.tokenPrefix)) {
try {
String token = tokenHeader.replace(JWTConfig.tokenPrefix, "");
Claims claims = Jwts.parser()
.setSigningKey(JWTConfig.secret)
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
String userId = claims.getId();
if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(userId)) {
List<GrantedAuthority> authorities = new ArrayList<>();
String authority = claims.get("authorities").toString();
if (!StringUtils.isEmpty(authority)) {
List<Map<String, String>> authorityMap = JSONObject.parseObject(authority, List.class);
for (Map<String, String> role : authorityMap) {
if (!StringUtils.isEmpty(role)) {
authorities.add(new SimpleGrantedAuthority(role.get("authority")));
}
}
}
SelfUserEntity selfUserEntity = new SelfUserEntity();
selfUserEntity.setUsername(claims.getSubject());
selfUserEntity.setUserId(Long.parseLong(claims.getId()));
selfUserEntity.setAuthorities(authorities);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(selfUserEntity, userId, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (ExpiredJwtException e) {
log.info("Token过期");
} catch (Exception e) {
log.info("Token无效");
}
}
filterChain.doFilter(request, response);
}
}Permission Expressions
Spring Security provides a rich set of SpEL expressions such as hasRole('ADMIN'), hasAnyRole('ADMIN','USER'), hasAuthority('READ'), hasPermission('/admin/userList','sys:user:info'), and logical combinations like hasRole('ADMIN') and hasPermission(...). Custom PermissionEvaluator enables fine‑grained permission checks based on data stored in the database.
Testing
A test creates a user with a BCrypt‑encoded password, assigns the USER role, and verifies login, token generation, and access control. Switching to the ADMIN role demonstrates successful access to admin‑only endpoints.
Gitee: https://gitee.com/liselotte/spring-boot-security-demo GitHub: https://github.com/xuyulong2017/my-java-demo
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 High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
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.
